我有一个使用标准cout进行日志记录的多线程应用程序,例如
cout << "some text" << endl;
问题是由于多个线程对共享cout的非线程安全访问,程序偶尔会遇到崩溃。
说,我的程序名为prg.exe,我们运行为prog.exe&gt; t.log
由单个线程生成的所有日志混合在一起,即使没有问题,只要竞争线程在访问cout时发生争用,一个尝试刷新,另一个尝试将某些东西放在那里,那么坏的部分就是崩溃,导致崩溃。
由于cout在现有代码中有很多用途,由不同的线程使用,因此很难将所有cout更改为其他内容。所以,我试图采用以下内容:
std::streambuf * redirect_output(char * filenm, std::ofstream& filestr)
{
std::streambuf *newsb, *oldsb;
filestr.open(filenm);
oldsb = std::cout.rdbuf(); // back up cout's streambuf
newsb = filestr.rdbuf(); // get file's streambuf
std::cout.rdbuf(newsb); // assign streambuf to cout
return oldsb;
}
void restore_output(std::streambuf * oldsb, std::ofstream& filestr)
{
std::cout.rdbuf(oldsb); // restore cout's original streambuf
filestr.close();
}
void showFileContent(char *filenm)
{
std::ifstream infile;
infile.open(filenm);
//read data from file
cout << "------------- " << filenm << " -------------" << endl;
cout << infile.rdbuf();
infile.close();
}
每个线程在启动时尝试调用redirect_output将cout重定向到一个文件,每个线程分开一个文件。所以,例如,如果我们有3个线程,我们有t1.log,t2.log,t3.log 最后我们通过每个线程调用restore_output,最后在main中,我们通过
合并各个日志文件showFileContent("t1.log");
showFileContent("t2.log");
showFileContent("t3.log");
我的问题是,每个线程重定向到单个日志文件并在主函数末尾合并是否明智和安全? 我们可以通过各个线程将各个日志文件合并到逻辑同步点吗?
其他选项可能是让线程安全的单例类封装内置的i / o并使用该单例对象而不是cout,例如: SPCL_IOSTREAM :: getOStream()
答案 0 :(得分:3)
这是搜索/替换(或sed)是您最好的朋友的情况之一。只需花时间将std::cout
用法替换为对您最喜欢的(线程安全)日志库的调用,并为自己省去很多麻烦。
同样合并main()末尾的文件听起来很复杂(你需要根据每个条目的时间戳进行合并)来维护顺序 - 而且不可靠 - 如果你在main()结束之前崩溃怎么办? / p>
答案 1 :(得分:1)
尽管提到你有很多用户都使用std :: cout进行日志记录,但我建议你看看here是否真的想要遵循这个策略。 我的建议不是重定向cout而是提供同步的记录器类,它也可以提供输出选项,并使用它。
// Logger() is a singleton that returns a ref. to a different output stream
// based on its parameter.
// Default (no parameter) send everything to cout after synchronizing threads
Logger::addLog("log.txt"); // add an instance of a stream in a static
// map<string, ofstream>
// The dtor can delete the streams after flusing, or for better control, you can
// add methods like
Logger::delLog("log.txt"); // flush and erase the map entry, this log is gone
Logger::log(0, "Some message");
Logger::log("log.txt", "another message");
// Logger::log() can also be a variadic method that takes a log key, a format,
// and parameters like printf
Logger::log(0, "Value=%d", int_value);
您甚至可以考虑通过返回一个函数来改进它,该函数在ctor()中为日志流锁定互斥锁,并在数据成员中保留对它的引用,然后让一个operator()提供对该流的访问,并在其dtor()中解锁它。这个方法可以让你使用&lt;&lt;流支持的运算符,但是,您需要确保每次都破坏仿函数,以避免死锁。
评论后编辑:
是的,你可以重定向它。在StackOverflow中查看此答案here。 此外,如果您还想在遗留代码中捕获任何printf()调用,则需要使用dup2()在stdio级别重定向。相关链接here和here。
希望这有帮助。