1

我正在开发一个多线程 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);

我认为这很丑陋,但它似乎有效。

顺便说一句,感谢另一个问题(向 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每次都创建一个对象。

提前致谢。

4

3 回答 3

2

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

于 2013-08-11T11:14:25.837 回答
2

一旦您开始进行异步日志记录,如果您的应用程序崩溃,您也有丢失最后几个日志条目的风险。因此,您必须在退出之前捕获 SIGSEGV 和其他致命信号(不是 SIGINT)并记录它们。消息队列解决方案(记录器线程应封装在活动对象中)只是将外部锁/互斥锁的争用转移到单独的后台线程。可以在 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 );
于 2013-08-11T16:15:32.403 回答
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,则需要 make HANDLE mutexinto ,在合适的 .cpp 文件中static HANDLE mutex;添加一个,然后使用: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 解决方案 - 这是一个很好的解决方案。

于 2013-08-11T09:39:24.050 回答