【问题标题】:Concurrent log file access in C/C++C/C++ 中的并发日志文件访问
【发布时间】:2012-09-11 04:41:25
【问题描述】:

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

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?

谢谢!

【问题讨论】:

  • 如果cs 是一个局部变量,那么每次输入writeLog 时都会创建一个新的临界区(并在返回时泄漏它),所以它不是一个临界区全部。
  • 临界区对象必须是全局的,see MSDN

标签: c++ c multithreading winapi concurrency


【解决方案1】:

进行并发日志记录的最佳方法是使用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_;
};

【讨论】:

  • 别忘了在构造函数中InitializeCriticalSection =)
  • @paddy:如果您更喜欢 CRITICAL_SECTION - 那么是的,当然。但我仍然会推荐 boost mutex 之一(或 std:: 如果你有现代编译器)。初始化更简单,日志写入将是异常安全的。
【解决方案2】:

回答您的问题:

  1. 是的,并发日志记录确实需要临界区。

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

如 cmets 中所述,用于“保护”临界区的对象必须可由所有线程访问,例如全局变量或单例。

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      我正在写一个答案,然后断路器跳闸。由于我的答案仍在草稿中,我不妨继续。与提供单例类的答案大致相同,但我这样做更像 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。或者,如果您想在外部实时监控日志,则每次都必须调用它。

      是的,关键部分对性能有影响,但与写入文件的性能成本相比,这算不了什么。您可以每秒输入数千次关键部分而无需担心。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多