SHARING_VIOLATION Windows 上的多线程文件 IO
SHARING_VIOLATION with multi-threaded file IO on Windows
我有一些代码类似于这个最小复制示例(真实版本生成一些代码并编译它):
#include <fstream>
#include <string>
#include <thread>
#include <vector>
void write(unsigned int thread)
{
std::ofstream stream("test_" + std::to_string(thread) + ".txt");
stream << "test" << std::endl;
stream << "thread" << std::endl;
stream << "bad" << std::endl;
}
void test(unsigned int thread)
{
write(thread);
#ifdef _WIN32
const std::string command = "rename test_" + std::to_string(thread) + ".txt test_renamed_" + std::to_string(thread) + ".txt";
#else
const std::string command = "mv test_" + std::to_string(thread) + ".txt test_renamed_" + std::to_string(thread) + ".txt";
#endif
system(command.c_str());
}
int main()
{
std::vector<std::thread> threads;
for(unsigned int i = 0; i < 5; i++) {
// Remove renamed file
std::remove(("test_renamed_" + std::to_string(i) + ".txt").c_str());
threads.emplace_back(test, i);
}
// Join all threads
for(auto &t : threads) {
t.join();
}
return EXIT_SUCCESS;
}
我的理解是 std::ofstream
应该以良好的 RAII 方式运行,并在写入函数结束时关闭并刷新。在 Linux 上,它似乎就是这样做的。但是,在 Windows 10 上,我偶尔会收到“该进程无法访问该文件,因为它正在被另一个进程使用”的错误。我已经用 procmon 深入研究了它,看起来文件没有被父进程 (22224) 关闭,导致 SHARING_VIOLATION
可能导致错误:
尽管 procmon 跟踪看起来问题出在我的进程中,但我已尝试关闭病毒扫描程序。我还尝试使用 C 风格的 fopen、fprintf、fclose 并确保我使用 system
生成的进程不会通过清除底层文件句柄上的 HANDLE_FLAG_INHERIT 以某种方式继承文件句柄。 .这让我有些不知所措!有什么想法吗?
至少在 VS 2017 上,我可以从您的代码片段中确认文件已关闭。 (在 ofstream 的析构函数中,代码在句柄上调用 fclose)。
不过,我认为问题不在于 C++ 代码,而在于 OS.
的行为
使用 Windows,删除 OS 认为打开的文件的行为将被阻止。在 Unix 中,从目录中取消文件链接的行为是允许现有句柄继续充当孤立文件。所以在 unix 中,该操作永远不会是共享冲突,因为取消链接文件的行为是不同的操作。 linux 可以为最近的 windows 10 个构建选择语义。
Windows 上的 procmon 具有给定的高度。这意味着病毒扫描程序执行的任何操作都可能对 procmon 隐藏,并且会给出错误的答案。
一个进程也可以在打开的文件上复制一个句柄,这也会导致这个问题,但不会显示正在关闭的句柄。
我们可以使用Win32重写文件写入API:
void writeRaw(unsigned int thread)
{
const auto str = "test_" + std::to_string(thread) + ".txt";
auto hFile = CreateFileA(str.c_str(), GENERIC_WRITE,
FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, 0, nullptr);
DWORD ret{};
WriteFile(hFile, str.data(), str.size(), &ret, nullptr);
CloseHandle(hFile);
}
运行 由于 windows 的工作方式,测试仍然存在文件共享冲突。当最后一个句柄关闭时,文件系统驱动程序执行 IRP_MJ_CLEANUP IOCTL 以完成与文件相关的任何处理。
例如,防病毒软件会尝试扫描文件(并顺便锁定文件 =))。此外,文档 MSDN IRP_MJ_CLEANUP 指出:
It is important to note that when all handles to a file object have been closed, this does not necessarily mean that the file object is no longer being used. System components, such as the Cache Manager and the Memory Manager, might hold outstanding references to the file object. These components can still read to or write from a file, even after an IRP_MJ_CLEANUP request is received.
结论:如果进程在关闭句柄后不久尝试对文件执行某些操作,则预计会在 windows 中收到文件共享冲突,因为底层系统组件仍在处理文件关闭请求.
问题的最可能原因是当您在 windows 中删除文件时,它并没有立即被删除(它只是被标记为删除)。它 can/will 需要几毫秒(如果你很不走运,最多几秒)才能真正删除。
来源:Niall Douglas 在“使用 Boost.AFIO 在文件系统上更好地互斥”中关于 10m:10s https://www.youtube.com/watch?v=9l28ax3Zq0w
我有一些代码类似于这个最小复制示例(真实版本生成一些代码并编译它):
#include <fstream>
#include <string>
#include <thread>
#include <vector>
void write(unsigned int thread)
{
std::ofstream stream("test_" + std::to_string(thread) + ".txt");
stream << "test" << std::endl;
stream << "thread" << std::endl;
stream << "bad" << std::endl;
}
void test(unsigned int thread)
{
write(thread);
#ifdef _WIN32
const std::string command = "rename test_" + std::to_string(thread) + ".txt test_renamed_" + std::to_string(thread) + ".txt";
#else
const std::string command = "mv test_" + std::to_string(thread) + ".txt test_renamed_" + std::to_string(thread) + ".txt";
#endif
system(command.c_str());
}
int main()
{
std::vector<std::thread> threads;
for(unsigned int i = 0; i < 5; i++) {
// Remove renamed file
std::remove(("test_renamed_" + std::to_string(i) + ".txt").c_str());
threads.emplace_back(test, i);
}
// Join all threads
for(auto &t : threads) {
t.join();
}
return EXIT_SUCCESS;
}
我的理解是 std::ofstream
应该以良好的 RAII 方式运行,并在写入函数结束时关闭并刷新。在 Linux 上,它似乎就是这样做的。但是,在 Windows 10 上,我偶尔会收到“该进程无法访问该文件,因为它正在被另一个进程使用”的错误。我已经用 procmon 深入研究了它,看起来文件没有被父进程 (22224) 关闭,导致 SHARING_VIOLATION
可能导致错误:
system
生成的进程不会通过清除底层文件句柄上的 HANDLE_FLAG_INHERIT 以某种方式继承文件句柄。 .这让我有些不知所措!有什么想法吗?
至少在 VS 2017 上,我可以从您的代码片段中确认文件已关闭。 (在 ofstream 的析构函数中,代码在句柄上调用 fclose)。
不过,我认为问题不在于 C++ 代码,而在于 OS.
的行为使用 Windows,删除 OS 认为打开的文件的行为将被阻止。在 Unix 中,从目录中取消文件链接的行为是允许现有句柄继续充当孤立文件。所以在 unix 中,该操作永远不会是共享冲突,因为取消链接文件的行为是不同的操作。 linux 可以为最近的 windows 10 个构建选择语义。
Windows 上的 procmon 具有给定的高度。这意味着病毒扫描程序执行的任何操作都可能对 procmon 隐藏,并且会给出错误的答案。
一个进程也可以在打开的文件上复制一个句柄,这也会导致这个问题,但不会显示正在关闭的句柄。
我们可以使用Win32重写文件写入API:
void writeRaw(unsigned int thread)
{
const auto str = "test_" + std::to_string(thread) + ".txt";
auto hFile = CreateFileA(str.c_str(), GENERIC_WRITE,
FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, 0, nullptr);
DWORD ret{};
WriteFile(hFile, str.data(), str.size(), &ret, nullptr);
CloseHandle(hFile);
}
运行 由于 windows 的工作方式,测试仍然存在文件共享冲突。当最后一个句柄关闭时,文件系统驱动程序执行 IRP_MJ_CLEANUP IOCTL 以完成与文件相关的任何处理。 例如,防病毒软件会尝试扫描文件(并顺便锁定文件 =))。此外,文档 MSDN IRP_MJ_CLEANUP 指出:
It is important to note that when all handles to a file object have been closed, this does not necessarily mean that the file object is no longer being used. System components, such as the Cache Manager and the Memory Manager, might hold outstanding references to the file object. These components can still read to or write from a file, even after an IRP_MJ_CLEANUP request is received.
结论:如果进程在关闭句柄后不久尝试对文件执行某些操作,则预计会在 windows 中收到文件共享冲突,因为底层系统组件仍在处理文件关闭请求.
问题的最可能原因是当您在 windows 中删除文件时,它并没有立即被删除(它只是被标记为删除)。它 can/will 需要几毫秒(如果你很不走运,最多几秒)才能真正删除。
来源:Niall Douglas 在“使用 Boost.AFIO 在文件系统上更好地互斥”中关于 10m:10s https://www.youtube.com/watch?v=9l28ax3Zq0w