0

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

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

4 回答 4

4

进行并发日志记录的最佳方法是使用现有的 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_;
};
于 2012-09-11T04:50:44.010 回答
3

要回答您的问题:

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

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

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

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

于 2012-09-11T05:07:02.747 回答
2

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

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

于 2012-09-11T07:57:12.300 回答
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如果您希望您的应用程序崩溃,您也可能希望定期调用。或者,如果您想在外部实时监控日志,则每次都必须调用它。

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

于 2012-09-11T05:36:21.100 回答