假设程序具有缓存机制,其中在某些特定计算的末尾,该程序会将该计算的输出写入磁盘,以免在以后重新运行该程序时重新计算。这样做是为了进行大量计算,并将每个输出保存到单独的文件中(每个计算一个)。数据通过标准C ++流写入文件:
void* data = /* result of computation */;
std::size_t dataSize = /* result of computation */;
std::string cacheFile = /* unique filename for to this computation */;
std::ofstream out(cacheFile, std::ios::binary);
out << dataSize;
out.write(static_cast<const char *>(data), dataSize);
问题:对于多个线程(或进程),对于相同的计算和相同的输出文件,同时尝试进行此操作是否安全?
该计算是确定性的,因此写入给定文件的数据将始终相同。因此,只要至少一个成功,某些线程或进程是否无法写入文件就没有关系。在我进行的手动测试中,没有程序失败或数据损坏发生,并且始终使用正确的内容创建文件,但这可能与平台有关。作为参考,在我们的特定情况下,数据的大小范围为2到50 KB。
多个线程(或进程)同时尝试进行相同的计算,使用相同的输出文件是否安全?
这是一个竞争条件,当多个线程尝试写入同一文件时,您可能最终会损坏文件。无法保证ofstream::write
是原子的,并且取决于特定的文件系统。
针对您的问题的正确解决方案:
rename
临时文件为其最终名称。如果rename
失败,则文件已经存在。多个线程同时写入文件并不安全。线程写入文件的顺序也是不可预测的。
但是,如果您使用某种线程同步并且只有一个类来管理文件,则可以这样做。您可以使用rename
对象,在写入文件之前将其锁定。这是一个非常简单的类可以做到这一点:
std::mutex
#include <fstream>
#include <vector>
#include <mutex>
class FileManager
{
public:
FileManager(const std::string& filepath) : mFs(filepath, std::ios::binary) {}
~FileManager() = default;
void Write(const std::vector<char> &data)
{
std::lock_guard<std::mutex> lk(mMutex);
mFs.write(&data[0], data.size());
}
private:
std::ofstream mFs;
std::mutex mMutex;
};
int main()
{
FileManager fm("D:\\Temp\\data.dat");
std::vector<char> data = { 'H','e','l','l','o' };
fm.Write(data);
}
将在写操作期间锁定您的互斥锁,以确保它是原子的。它也是RAII,因此在超出范围时会解锁互斥锁。
使用共享指针或对线程中相同std::lock_guard对象的引用。