在用C++编写的程序中,在Windows下使用MinGW-w64编译,我在不同的线程中同时读取多个文件。由于文件名可能有非ASCII字符,因此我不能使用C++标准库std::ifstream
,因为它不支持wchar
文件名。所以我需要使用Win32 API中的_wfopen
的C库。难道不是非租户?
但是,我收到了一个非常奇怪的错误,我在MCVE中进行了复制。使用fread()读取n个字节后,_ftelli64
的结果有时不会增加n,但会减少或减少几个字节。
随着单个线程读取,问题消失了,并与std::ifstream
为好。
它表现得好像fread中存在竞争条件,然后它就不可重入。
在以下示例中,我取代_wfopen
由fopen
作为错误仍然存在。
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
#include <thread>
constexpr const int numThreads = 8;
constexpr const int blockSize = 65536+8;
constexpr const int fileBlockCount = 48; //3MB files
void readFile(const std::string & path)
{
std::cout << "Reading file " << path << "\n";
std::vector<char> buffer(blockSize);
FILE * f = fopen(path.c_str(), "rb");
for(int i=0;i<fileBlockCount;++i)
{
int64_t pos_before = _ftelli64(f);
int64_t n = fread(buffer.data(), 1, buffer.size(),f);
int64_t pos_after = _ftelli64(f);
int64_t posMismatch = (int64_t)pos_after-(pos_before+n);
if(ferror(f))
{
std::cout << "fread error\n";
}
if(posMismatch!=0)
{
std::cout << "Error " << path
<< "/ftell before " << pos_before
<< "/fread returned " << n
<< "/ftell after " << pos_after
<< "/mismatch " << posMismatch << "\n";
}
}
fclose(f);
}
int main()
{
//Generate file names
std::vector<std::string> fileNames(numThreads);
for(int i=0;i<numThreads;++i)
{
std::ostringstream oss;
oss << i << ".dat";
fileNames[i] = oss.str();
}
//Create dummy data files
for(int i=0;i<numThreads;++i)
{
std::ofstream f(fileNames[i], std::ios_base::binary);
for(int j=0;j<blockSize*fileBlockCount;++j)
{
f.put((char)(j&255));
}
}
//Read data files in separate threads
std::vector<std::thread> threads;
for(int i=0;i<numThreads;++i)
{
threads.emplace_back(readFile, fileNames[i]);
}
//This waits for the threads to finish
for(int i=0;i<numThreads;++i)
{
threads[i].join();
}
threads.clear();
std::cout << "Done";
}
输出是随机的东西,如:
Error 3.dat/ftell before 65544/fread returned 65544/ftell after 131089/mismatch 1
Error 7.dat/ftell before 0/fread returned 65544/ftell after 65543/mismatch -1
Error 7.dat/ftell before 65543/fread returned 65544/ftell after 131088/mismatch 1
Error 3.dat/ftell before 2162953/fread returned 65544/ftell after 2228498/mismatch 1
Error 7.dat/ftell before 2162952/fread returned 65544/ftell after 2228497/mismatch 1
Error 3.dat/ftell before 3080570/fread returned 65544/ftell after 3146112/mismatch -2
Error 7.dat/ftell before 3080569/fread returned 65544/ftell after 3146112/mismatch -1
Error 2.dat/ftell before 65544/fread returned 65544/ftell after 131089/mismatch 1
Error 6.dat/ftell before 0/fread returned 65544/ftell after 65543/mismatch -1
Error 6.dat/ftell before 65543/fread returned 65544/ftell after 131088/mismatch 1
Error 2.dat/ftell before 2162953/fread returned 65544/ftell after 2228498/mismatch 1
Error 6.dat/ftell before 2162952/fread returned 65544/ftell after 2228497/mismatch 1
Error 2.dat/ftell before 3080570/fread returned 65544/ftell after 3146112/mismatch -2
Error 6.dat/ftell before 3080569/fread returned 65544/ftell after 3146112/mismatch -1
编辑:如果我通过ftell
更换_ftelli64
这似乎与_ftelli64
,这个问题是不存在了 那么这是一个破碎而不是实体_ftelli64
?
好吧,显然'fread()'*可以是不可重入的。我猜这个问题是它是否在Win32上是不可重入的,也许这是否符合要求。 –
根据microsoft(查看https://msdn.microsoft.com/en-us/library/0ys3hc0b.aspx中的评论)从'ftell()'或(如您使用的那样)'_ftelli64() '由最后的I/O操作决定。除非有可能出现一些不匹配现象,例如你所看到的,否则他们不会这样说。 – Peter
@Peter,这些注释似乎主要针对以追加模式打开的文件,其中下一次写入将始终发生在文件末尾,可能不会在最后一次读取后立即写入。 –