C / C ++中的并发日志文件访问

时间:2012-09-11 04:41:25

标签: c++ c multithreading winapi concurrency

我正在创建一个多线程程序,并且几个线程可能需要调用全局函数

writeLog(const char* pMsg);

并且writeLog将实现像tihs:

void writeLog(const char* pMsg)
{
   CRITICAL_SECTION cs;

   // initialize critical section
   ...

   EnterCriticalSection(&cs);

   // g_pLogFilePath is a global variable.
   FILE *file;
   if (0!=fopen_s(&file, g_pLogFilePath, "r+"))  
      return;

   fprintf(file, pMsg);

   fclose(file):
   LeaveCriticalSection(&cs);
}

我的问题是:

1) is it the best way to do concurrent logging? i.e., using critical section.

2) since I will write log in many places in the threads, 
and since each log writing will involve open/close file,
does the io will impact the performance significantly?

谢谢!

4 个答案:

答案 0 :(得分:4)

执行并发日志记录的最佳方法是使用existing log library for C++之一。 它们有许多你可能想要使用的功能(不同的appender,格式化,并发等)。

如果您仍想拥有自己的解决方案,可能会遇到以下情况: 初始化一次并保持状态的简单单例(文件处理程序和互斥锁)

class Log
{
public:

    // Singleton
    static Log & getLog() 
    {
        static Log theLog;
        return theLog;
    }

    void log(const std::string & message)
    {
         // synchronous writing here
    }
private:
    // Hidden ctor
    Log()
    {
         // open file ONCE here
    }

    // Synchronisation primitive - instance variable
    // CRITICAL_SECTION or Boost mutex (preferable)
    CRITICAL_SECTION cs_;

    // File handler: FILE * or std::ofstream
    FILE * handler_;
};

答案 1 :(得分:3)

回答你的问题:

  1. 是的,并发日志确实需要一个关键部分。

  2. 是的,记录可能确实会影响性能。

  3. 正如评论中所提到的,用于“保护”关键部分的对象必须可被所有线程访问,例如全局变量或单例。

    关于日志记录性能,IO可能代价高昂。一种常见的方法是使用一个日志记录对象来缓冲要记录的消息,并且只在缓冲区已满时写入。这将有助于提高性能。另外,考虑有几个日志级别:DEBUG,INFO,WARNING,ERROR。

答案 2 :(得分:2)

CS是保护日志记录的合理方法,是的。为了避免在每个线程的每次调用时造成打开/写入/关闭,通常将字符串排队(如果尚未进行malloced / newed,则可能需要将其复制)到单独的日志线程。然后从日志记录调用缓冲阻止磁盘延迟。任何延迟写入等优化都可以在日志线程中实现。

或者,正如其他海报所建议的那样,只需使用已经实现了所有这些内容的日志框架。

答案 3 :(得分:1)

我正在写一个答案,然后一个断路器跳闸了。由于我的答案仍在草案中,我可以继续。与提供单例类的答案大致相同,但我做的更像C语言。这都在一个单独的源文件中(例如Logging.cpp)。

static CRITICAL_SECTION csLogMutex;
static FILE *fpFile = NULL;
static bool bInit = false;

bool InitLog( const char *filename )
{
    if( bInit ) return false;
    bInit = true;
    fpFile = fopen( filename, "at" );
    InitializeCriticalSection(&csLogMutex);
    return fpFile != NULL;
}

void ShutdownLog()
{
    if( !bInit ) return;
    if( fpFile ) fclose(fpFile);
    DeleteCriticalSection(&csLogMutex);
    fpFile = NULL;
    bInit = false;
}

在你的应用程序入口/出口中调用它们......至于日志记录,我更喜欢使用变量参数列表,所以我可以进行printf样式的日志记录。

void writeLog(const char* pMsg, ...)
{
   if( fpFile == NULL ) return;

   EnterCriticalSection(&csLogMutex);

   // You can write a timestamp into the file here if you like.

   va_list ap;
   va_start(ap, pMsg);
   vfprintf( fpFile, pMsg, ap );
   fprintf( fpFile, "\n" );        // I hate supplying newlines to log functions!
   va_end(ap);

   LeaveCriticalSection(&csLogMutex);
}

如果您计划在DLL中进行日志记录,则无法使用此静态方法。相反,您需要使用_fsopen打开文件并拒绝读/写共享。

如果您希望自己的应用程序崩溃,也可以定期致电fflush。或者,如果您想要实时外部监控日志,则必须每次都调用它。

是的,关键部分会对性能产生影响,但与写入文件的性能成本无关。您可以毫不费力地每秒输入关键部分数千次。