避免输出交错

时间:2013-08-11 09:17:25

标签: c++ multithreading locking

我正在研究多线程c ++应用程序,实际上我遇到了交错控制台输出的问题,这是由cout / cerr的并发线程调用引起的。注意:我不能使用boost / QT /其他框架,只能使用标准的c ++。

作为临时修复我正在使用这个类:(实际上这是一个win32代码片段,这就是为什么使用CRITICAL_SECTION作为临时解决方法);

class SyncLogger
{
public:
    SyncLogger() { InitializeCriticalSection(&crit); }
    ~SyncLogger() { DeleteCriticalSection(&crit); }
    void print(std::ostringstream &os) {
        EnterCriticalSection(&crit);
        std::cout << os.str();
        os.str("");   // clean stream
        LeaveCriticalSection(&crit);
    }
private:
    CRITICAL_SECTION crit;
};

用法如下:

...
ostringstream ss;
ss << "Hello world, I'm a thread!" << endl;
syncLogger.print(ss);

我觉得这很难看,但似乎有效。

顺便说一下这个问题(Best way to add a "prompt" message to std::cout)我创建了以下日志记录类:

class MyLogger
{
    std::ostream & out;
    std::string const msg;
public:
    MyLogger(std::ostream & o, std::string s)
    : out(o)
    , msg(std::move(s))
    { }

    template <typename T>
    std::ostream & operator<<(T const & x)
    {
        return out << msg << x;
    }
};

那么,确实存在一种在类MyLogger中提供内置锁定的方法(使用临界区或win32互斥锁)? 我最好的是,任何线程都能够以同步方式打印消息,只需使用

即可
myLog << "thread foo log message" << endl;

并且无需每次都创建ostringstream对象。

提前致谢。

3 个答案:

答案 0 :(得分:2)

我认为解决这个问题的最佳方法是建立一个消息队列。您确保只有一个线程可以使用互斥锁一次写入队列,并且您有另一个线程从队列中读取并实际执行输出。这样,您的工作线程就不必等待控制台输出被写入。如果多个工作线程正在尝试输出,这一点尤其重要,因为这样他们不仅要等待自己的输出完成,还要等待其他线程的输出,这可能会严重降低程序的速度

答案 1 :(得分:2)

一旦您开始执行异步日志记录,如果您的应用程序崩溃,您还可能会丢失最后几个日志条目。所以你必须捕获SIGSEGV和其他致命信号(不是SIGINT)并在退出之前记录它们。 消息队列解决方案(记录器线程应该封装在active object中)只是将锁定/互斥锁外部的争用转移到单独的后台线程。可以在C ++中实现无锁队列,并且它们非常适合日志记录。但是如果你不关心性能和可扩展性,只能尝试避免输出交错,你可以像Mats Petersson所说的那样做。 它有点像这样:

class AsyncLogFile {
public:
  void write( string str )
    { a.Send( [=] { log.write( str ); } ); }
private:
  File log;    // private file
  ActiveObject a;    // private helper (queue+thread)
};
AsyncLogFile logFile;

// Each caller just uses the active object directly
string temp = …;
temp.append( … );
temp.append( … );
logFile.write( temp );

答案 2 :(得分:1)

在日志记录机制中添加互斥锁不应该那么难。

假设MyLogger只有一个实例,那么这样的事情应该有效:

class MyLogger
{
    std::ostream & out;
    std::string const msg;
    HANDLE mutex;
public:
    MyLogger(std::ostream & o, std::string s)
    : out(o)
    , msg(std::move(s))
    { 
       mutex = CreateMutex(0, FALSE, 0);
    }

    template <typename T>
    std::ostream & operator<<(T const & x)
    {
        WaitForSingleObject(mutex, INFINITE);
        out << msg << x;
        ReleaseMutex(mutex);
        return out;
    }
};

如果MyLogger有多个实例,那么您需要将HANDLE mutex变为static HANDLE mutex;,在合适的.cpp文件中的某处添加HANDLE MyLogger::mutex = 0;,然后使用:

    MyLogger(std::ostream & o, std::string s)
    : out(o)
    , msg(std::move(s))
    { 
       if (!mutex) { 
           HANDLE tmp = CreateMutex(0, FALSE, "MyLoggerMutex");
           if (tmp) 
           {
              mutex = tmp;
           }
       }
    }

通过使用名称和临时变量,我们可以避免在创建多个互斥锁时出现竞争条件(因为系统中只能有一个MyLoggerMutex。[如果您的应用程序运行的多个实例也会变得更复杂]同时!]。因为我假设只有一个应用程序的实例,我也没有考虑到互斥锁可能已经存在......或者如何销毁最后一个实例..当应用程序退出时它将被销毁......

我喜欢评论中的dasblinkenlight解决方案 - 这是一个很好的解决方案。

相关问题