我正在研究多线程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
对象。
提前致谢。
答案 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解决方案 - 这是一个很好的解决方案。