弗雷德可以不租吗?
Can fread be non rentrant?
在一个用C++编写并在Windows下用MinGW-w64编译的程序中,我在单独的线程中同时读取了几个文件。由于文件名可能包含非 ASCII 字符,因此我无法使用C++标准库std::ifstream
因为它不支持wchar
文件名。所以我需要将 C 库与 Win32 API 中的_wfopen
一起使用。
但是,我得到了一个非常奇怪的错误,我已经在MCVE中重现了该错误。使用 fread() 读取 n 个字节后,_ftelli64
的结果有时不会增加 n,而是少或多几个字节。
通过单线程读取,问题消失了,std::ifstream
也
消失了。它的行为就好像 fread 中存在竞争条件,然后是不可重入的。
在下面的示例中,我用fopen
替换了_wfopen
,因为错误仍然存在。
#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 errorn";
}
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
编辑:这似乎与_ftelli64
有关
如果我用ftell
替换_ftelli64
,问题不再存在 那么这是_ftelli64
的破碎而不是重入实现吗?
由于您主要询问的是 C 标准库,因此 C 标准说:
每个流都有一个关联的锁,用于防止多个执行线程访问流时发生数据争用,并限制多个线程执行的流操作的交错。一次只能有一个线程持有此锁。锁是可重入的:单个线程可以在给定时间多次持有锁。
所有读取、写入、定位或查询流位置的函数都会在访问流之前锁定流。它们在访问完成后释放与流关联的锁。
(C2011 7.21.2/7-8)
C++人们应该注意,在 C 中,"流"是指通过FILE *
访问的那种东西。fread()
,标准部分说,
流的文件位置指示器(如果已定义)按成功读取的字符数前进。
和
fread 函数返回成功读取的元素数
但也
如果发生错误,则流的文件位置指示器的结果值不确定。
(C2011, 7.21.8.1/2-3)
它似乎没有将到达流的末尾描述为错误。
虽然 C11 没有明确说明fread()
必须是线程安全的,但它确实承认多线程程序的存在并定义它们的语义。 它指定在此类程序中,
每个线程的执行按照本标准的其余部分的定义进行。
(C2011, 5.1.2.4/1)
这不允许fread()
在不同流上并行调用时无法按照记录的行为,并且我之前引用的锁定要求可以防止数据争用和参与的未定义行为,即使它在同一流上并行调用也是如此。
_ftelli64()
不是ISO C中的标准库函数,但Win32文档指定其行为的方式与指定ftell()
行为的术语相同,这是一个标准库函数。双
检索与
stream
关联的文件指针(如果有)的当前位置。该位置表示为相对于流开头的偏移量。
(Microsoft C 库文档)
Microsoft的"文件指针"与ISO C的"文件位置"是一回事。 总的来说,我可以看到观察到的行为符合性的唯一方法是,如果一些fread()
调用遇到错误。 您可以通过在fread()
返回 0 的情况下调用ferror()
来检查这一点。 如果有错误,那么所有的赌注都关闭了。
- 瓦尔格林德:数学函数"Conditional jump or move depends on uninitialised value(s)"
- 为什么兰德每次都给我几乎相同(但略有不同)的数字
- 为什么瓦尔格林德在不释放恶意内存后没有报告任何问题?
- 弗洛伊德·沃歇尔无限
- 有没有一种方法可以使用弗洛伊德-沃歇尔算法给出最短路径,其中存在负权重循环而不允许重叠边缘?
- 弗洛伊德最短路径算法C++
- 弗雷德可以不租吗?
- 使用弗洛伊德-斯坦伯格抖动不起作用
- 弗洛伊德-沃歇尔算法的路径重建不适用于某些值
- 弗洛伊德三角形变奏曲
- .rc 文件中的温德雷斯错误
- 弗洛伊德-沃歇尔算法实现的问题
- 使用改进的弗洛伊德战争打印最短路径黑白给定节点
- 雷文德利希教程 - 太空游戏
- 虽然弗洛伊德的周期查找算法中使用的条件
- 使用两个语句并行化 while 循环(弗洛伊德循环检测算法)
- 弗洛伊德的寻周期算法什么时候会失败?
- 亚历山德雷斯库的思想与现代C++相关吗?
- 你会如何将亚历山德雷斯库的预期与<T> void 函数一起使用?
- 弗洛伊德算法(最短路径)问题 - C++