将线程安全性添加到简单的日志记录功能?

时间:2012-03-25 17:48:03

标签: c++ windows multithreading thread-safety iostream

从我读到的standard output streams are generally not thread safe开始。我有一个C ++应用程序(基于Windows,使用Visual Studio 2005),它具有非常简单的日志记录功能:

void logText(string text)
{
    if(g_OutputLogEnabled && g_OutputLog.is_open())
    {
        string logDate = getDateStamp("%Y-%m-%d %H:%M:%S");
        g_OutputLog << "[" << logDate << "]: " << text << endl;
    }

    cout << text << endl; // Also echo on stdout
}

在此示例中,g_OutputLog是一个流,g_OutputLogEnabled是一个布尔值。

我一直在我的主应用程序中使用这个小函数没有问题,但我现在想将其用途扩展到一些子线程。这些线程在工作完成后可以正常工作和异步打印数据。

问题:如何在此例程中添加简单的行级线程安全性?所有我真正关心的是,我的日志中的每一行都保持不变。在这种情况下,性能不是问题,但(一如既往)更快更好。

我知道我可以使用第三方日志包,但我想自己做,所以我可以了解它是如何工作的。我的多线程知识不应该是它应该是什么,我正在努力改进它。

我听过critical sections这个词,我有点意识到互斥锁和信号量,但在这种情况下我会使用哪个?有一个干净,简单的解决方案吗?提前感谢任何建议。

3 个答案:

答案 0 :(得分:6)

使用范围锁定,例如:

void logText(string text)
{
    if(g_OutputLogEnabled && g_OutputLog.is_open())
    {
        string logDate = getDateStamp("%Y-%m-%d %H:%M:%S");

        boost::scoped_lock (g_log_mutex);  //lock
        g_OutputLog << "[" << logDate << "]: " << text << endl;

    } //mutex is released automatically here

    boost::scoped_lock (g_cout_log_mutex); //lock on different mutex!
    cout << text << endl; // Also echo on stdout
}

如果你的编译器支持,你可以使用std::unique_lock


如果你不能使用Boost,如果你没有scoped_lock,你将如何实施std::unique_lock

首先定义mutex类:

#include <Windows.h>

class mutex : private CRITICAL_SECTION  //inherit privately!
{
public:
     mutex() 
     {
        ::InitializeCriticalSection(this);
     }
     ~mutex() 
     {
        ::DeleteCriticalSection(this);
     }
private:

     friend class scoped_lock;  //make scoped_lock a friend of mutex

     //disable copy-semantic 
     mutex(mutex const &);           //do not define it!
     void operator=(mutex const &);  //do not define it!

     void lock() 
     {
        ::EnterCriticalSection(this);
     }
     void unlock() 
     {
        ::LeaveCriticalSection(this);
     }
};

然后将scoped_lock定义为:

class scoped_lock
{
      mutex & m_mutex;
  public:
      scoped_lock(mutex & m) : m_mutex(m) 
      {
          m_mutex.lock();
      }
      ~scoped_lock()
      {
          m_mutex.unlock();
      }
};

现在你可以使用它们了。

答案 1 :(得分:0)

鉴于该函数显然不是面向性能的,在写入流之前添加一个简单的互斥锁并锁定。

当然出于安全原因,应自动释放锁定,因此请务必使用RAII。

我建议你看一下Boost.Threads库。

答案 2 :(得分:0)

是的,您应该为代码添加某种保护。你不需要任何异国情调,你想要访问一个资源(你的流),而它可能正在被其他东西使用。两种类型的同步对象在此任务中表现良好:关键部分和互斥锁。有关锁定的更多详细信息,您可以开始阅读Wikipedia上的这篇文章。 Mutex通常较慢,但可以跨进程共享,这不是您的情况,因此您可以使用简单关键部分来同步您的线程。

如果你不打算使用第三方库(比如伟大的Boost),很少有提示。

EnterCriticalSection函数获取锁定时。如果资源被其他人锁定,则当资源将由其所有者释放时,您的线程将被暂停并重新激活(此外,您的锁定线程可能提升其优先级)。 对于短生存锁,这可能不是最佳解决方案因为暂停/恢复线程耗费时间和资源。因此,您可以设置 spin ;在暂停你的线程之前,操作系统将花费一点时间在该线程上什么也不做,它可能会给锁定所有者完成其工作并释放thred的时间。要使用此功能,您必须使用InitializeCriticalSectionAndSpinCount而不是InitializeCriticalSection初始化关键部分。

如果您打算使用Critical Section,您可以考虑包装类中所需的所有内容,您将使用变量范围来执行所有操作,并且您的代码将更加清晰(这只是一个示例,真正的实现不能是太天真了):

class critical_section
{
public:
 critical_section()
 {
  // Here you may use InitializeCriticalSectionAndSpinCount
  InitializeCriticalSection(&_cs);

  // You may not need this behavior, anyway here when you create
  // the object you acquire the lock too
  EnterCriticalSection(&_cs);
 }

 ~critical_section()
 {
   LeaveCriticalSection(&_cs);
   DeleteCriticalSection(&cs);
 }

private:
 CRITICAL_SECTION _cs;
};

在Unix / Linux下(或者你想要可移植),你应该使用Mutex的pthread.h函数。

锁定可能不够

这只是第一步,如果您的应用程序记录很多,您可能会减慢等待日志的所有线程的速度(不做任何先验,检查和配置文件)。如果是这种情况,您应该创建一个队列,您的logText()函数只需在队列中推送一个新的(预先格式化的)项目并调用PulseEvent来表示事件(使用CreateEvent创建的事件)。你将有第二个线程用WaitForSingleObject等待该事件,你的线程将被唤醒并且它将从队列中弹出一个项目。您可能仍然需要一些锁定机制(或者您可以编写自己的non-blocking concurrent queue以避免任何锁定。此解决方案更快,并且它不使用任何类型的锁,但我认为您应该做这样的事情只有你想要研究主题(线程),通常是为了简单的日志要求,你不需要添加这样的复杂性。