在不同的线程、不同的函数中将写入(日志记录)到同一个文件中?

5
在以下情况下,C++中是否有任何方法使写入文件线程安全?
void foo_one(){
lock(mutex1);
//open file abc.txt
//write into file
//close file
unlock(mutex1);
}

void foo_two(){
lock(mutex2);
//open file abc.txt
//write into file
//close file
unlock(mutex2);
}

在我的应用程序中(多线程),很可能会同时由两个不同的线程执行foo_one()和foo_two()。有没有办法使上述线程安全?
我考虑过使用文件锁(fcntl和/或lockf),但不确定如何使用它们,因为已在应用程序中使用了fopen(出于性能原因),而在某处声明说这些文件锁不应与fopen一起使用(因为它是缓冲的)。
PS:函数foo_one()和foo_two()位于两个不同的类中,它们之间没有共享数据的方式:(,而且遗憾的是设计是这样的,一个函数不能调用另一个函数。

相关链接:https://dev59.com/9FvUa4cB1Zd3GeqPvqqd - jogojapan
是的。你只需要一个代表文件的锁(称之为mutex3)。另外,如果你正在编写C++代码,应该使用RAII来锁定/解锁这些互斥量。 - Martin York
@LokiAstari,函数foo_one()和foo_two()位于两个不同的类中,它们之间没有共享资源的方法:(,而且遗憾的是设计使得一个函数不能调用另一个函数。 - k0n3ru
可以简单地在一个类中创建一个"写"函数,并在它自己的线程中调用。循环直到结束。每当你需要向文件写入内容时,只需将其推送到向量中,然后该线程会立即将其写入文件。 - M4rc
然后添加一个函数来执行写入和同步。 - Martin York
1
相关:http://stackoverflow.com/questions/439791/what-is-the-most-efficient-thread-safe-c-logger - Dmitry Ledentsov
5个回答

8

添加日志记录功能。
两个函数都调用日志记录函数(该函数执行适当的锁定操作)。

mutex  logMutex;
void log(std::string const& msg)
{
    RAIILock  lock(logMutex);

    // open("abc.txt");
    // write msg
    // close
}

如果性能是一个问题,锁定可能不是k0n3ru想要的东西。 - Dmitry Ledentsov
1
@devopsEMK:它是一个通用的类引用,使用RAII来锁定/解锁另一个对象。它们很容易编写。一个例子是std::lock_guard - Martin York

2
如果您真的需要日志记录器,不要试图通过简单地写入文件来完成,可以使用专用的记录器,从而将关注点从您正在编写的代码中分离出来。有许多线程安全的日志记录器可供选择:首先想到的是g2log。如果继续搜索,您会发现log4cplus,在这里讨论了一下,甚至还有一个极简主义的+1

1

如果函数 foo_one()foo_two() 的本质只是打开文件、向其中写入内容并关闭文件,那么可以使用同一个互斥锁来防止它们相互干扰:

void foo_one(){
  lock(foo_mutex);
  //open file abc.txt
  //write into file
  //close file
  unlock(foo_mutex);
}

void foo_two(){
  lock(foo_mutex);
  //open file abc.txt
  //write into file
  //close file
  unlock(foo_mutex);
}

当然,这假定这些是唯一的写入者。如果其他线程或进程向文件写入,那么锁定文件可能是一个好主意。

遗憾的是,函数foo_one()和foo_two()位于两个不同的类中,它们之间没有共享数据的方式。 - k0n3ru
1
@k0n3ru 那么锁定文件的想法呢?这里有一个描述该方法的问题:https://dev59.com/knI-5IYBdhLWcg3w8dYQ - jogojapan

1
你应该这样做,使用一个带有互斥锁和 ofstream 的结构体:
struct parser {
    ofstream myfile
    mutex lock
};

然后你可以将这个结构体(a)作为void*传递给foo1和foo2函数。

parser * a = new parser();

初始化互斥锁,然后您可以将结构体传递给这两个函数。
void foo_one(void * a){
     parser * b = reinterperet_cast<parser *>(a);
     lock(b->lock);
         b->myfile.open("abc.txt");
         //write into file
         b->myfile.close();
      unlock(b->mutex);
}

您可以对foo_two函数执行相同的操作。这将提供一种线程安全的方式来写入同一个文件。


虽然这种方法“可行”,而且你的答案写得很好,但在这里你只需要知道如何使用RAII来跟踪互斥锁。 - Johan Lundberg

0
尝试这段代码。我用MFC控制台应用程序完成了这个。
#include "stdafx.h"
#include <mutex>

CWinApp theApp;
using namespace std;


const int size_ = 100; //thread array size
std::mutex mymutex;
void printRailLock(int id) {
    printf("#ID :%", id);
    lock_guard<std::mutex> lk(mymutex); // <- this is the lock
    CStdioFile lastLog;
    CString logfiledb{ "_FILE_2.txt" };
    CString str;
    str.Format(L"%d\n", id);
    bool opend = lastLog.Open(logfiledb, CFile::modeCreate | CFile::modeReadWrite | CFile::modeNoTruncate);
    if (opend) {
        lastLog.SeekToEnd();
        lastLog.WriteString(str);
        lastLog.Flush();
        lastLog.Close();
    }
}
int main()
{
    int nRetCode = 0;
    HMODULE hModule = ::GetModuleHandle(nullptr);

    if (hModule != nullptr)
    {       
        if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
        {            
            wprintf(L"Fatal Error: MFC initialization failed\n");
            nRetCode = 1;
        }
        else
        {
            std::thread threads[size_];
            for (int i = 0; i < size_; ++i) {
                threads[i] = std::thread(printRailLock, i + 1);
                Sleep(1000);
            }
            for (auto& th : threads) { th.hardware_concurrency(); th.join(); }
        }
    }
    else
    {       
        wprintf(L"Fatal Error: GetModuleHandle failed\n");
        nRetCode = 1;
    }

    return nRetCode;
}

参考资料:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接