在用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,這些註釋似乎主要針對以追加模式打開的文件,其中下一次寫入將始終發生在文件末尾,可能不會在最後一次讀取後立即寫入。 –