xml*_*lmx 27 c++ logging multithreading iostream locking
我有一个多线程应用程序,它大量std::cout用于日志记录而没有任何锁定.在这种情况下,如何轻松添加锁机制以使std::cout线程安全?
我不想搜索每次出现std::cout并添加一行锁定代码.这太乏味了.
有更好的做法吗?
小智 25
虽然我不能确定这适用于std libs的每个编译器/版本,但在代码库中我使用std :: cout :: operator <<()它已经是线程安全的.
我假设你真正试图做的就是在与多个线程中的每个字符串连接多次运算符时,将std :: cout从混合字符串中停止.
字符串变得混乱的原因是因为操作符上存在"外部"竞争<<这可能导致这样的事情发生.
//Thread 1
std::cout << "the quick brown fox " << "jumped over the lazy dog " << std::endl;
//Thread 2
std::cout << "my mother washes" << " seashells by the sea shore" << std::endl;
//Could just as easily print like this or any other crazy order.
my mother washes the quick brown fox seashells by the sea sure \n
jumped of the lazy dog \n
Run Code Online (Sandbox Code Playgroud)
如果是这种情况,那么答案要比制作自己的线程安全cout或实现与cout一起使用的锁更简单.
在将它传递给cout之前,只需编写一下你的字符串
例如.
//There are other ways, but stringstream uses << just like cout..
std::stringstream msg;
msg << "Error:" << Err_num << ", " << ErrorString( Err_num ) << "\n";
std::cout << msg.str();
Run Code Online (Sandbox Code Playgroud)
这样你的叮咬就不会因为它们已经完全形成而造成乱码,而且在发送之前无论如何还要更好地完成你的弦乐.
MvG*_*MvG 18
我猜你可以实现你自己的类,它将std::osyncstream一个互斥量与它相关联.在cout新课改的会做三件事情:
operator <<包装流和传递的参数执行运算符这个不同的类会将锁和委托运算符<<保持为包装流.第二类的析构函数最终会破坏锁并释放互斥锁.
因此<<,只要所有输出都使用相同的互斥锁通过该对象,您将作为单个语句写入的任何输出(即作为单个调用序列)将以原子方式打印.
让我们把两个类<<和synchronized_ostream.如果locked_ostream是一个sync_cout包装的实例synchronized_ostream,那么序列
sync_cout << "Hello, " << name << "!" << std::endl;
Run Code Online (Sandbox Code Playgroud)
会导致以下操作:
std::cout 会得到锁synchronized_ostream::operator<< 将"Hello"的印刷委托给 synchronized_ostream::operator<<cout 会打印"你好",operator<<(std::ostream&, const char*)会构造一个synchronized_ostream::operator<<并将锁传递给它locked_ostream委托打印locked_ostream::operator<<到namecout 会打印出这个名字operator<<(std::ostream&, std::string)对于感叹号和结束操纵器,会发生相同的委托cout临时得到破坏,锁被释放Con*_*tor 11
我真的很喜欢尼古拉斯中给出的伎俩这个问题创建一个临时对象,并把保护代码的析构函数.
/** Thread safe cout class
* Exemple of use:
* PrintThread{} << "Hello world!" << std::endl;
*/
class PrintThread: public std::ostringstream
{
public:
PrintThread() = default;
~PrintThread()
{
std::lock_guard<std::mutex> guard(_mutexPrint);
std::cout << this->str();
}
private:
static std::mutex _mutexPrint;
};
std::mutex PrintThread::_mutexPrint{};
Run Code Online (Sandbox Code Playgroud)
然后,您可以std::cout从任何线程将其用作常规:
PrintThread{} << "my_val=" << val << std::endl;
Run Code Online (Sandbox Code Playgroud)
对象以常规方式收集数据ostringstream.一旦达到昏迷,对象就会被销毁并清除所有收集到的信息.
沿着 Conchylicultor 建议的答案,但不继承自std::ostringstream:
编辑:修复了重载运算符的返回类型并添加了重载std::endl。
编辑1:我已将其扩展为一个简单的仅标头库,用于记录/调试多线程程序。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <chrono>
static std::mutex mtx_cout;
// Asynchronous output
struct acout
{
std::unique_lock<std::mutex> lk;
acout()
:
lk(std::unique_lock<std::mutex>(mtx_cout))
{
}
template<typename T>
acout& operator<<(const T& _t)
{
std::cout << _t;
return *this;
}
acout& operator<<(std::ostream& (*fp)(std::ostream&))
{
std::cout << fp;
return *this;
}
};
int main(void)
{
std::vector<std::thread> workers_cout;
std::vector<std::thread> workers_acout;
size_t worker(0);
size_t threads(5);
std::cout << "With std::cout:" << std::endl;
for (size_t i = 0; i < threads; ++i)
{
workers_cout.emplace_back([&]
{
std::cout << "\tThis is worker " << ++worker << " in thread "
<< std::this_thread::get_id() << std::endl;
});
}
for (auto& w : workers_cout)
{
w.join();
}
worker = 0;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "\nWith acout():" << std::endl;
for (size_t i = 0; i < threads; ++i)
{
workers_acout.emplace_back([&]
{
acout() << "\tThis is worker " << ++worker << " in thread "
<< std::this_thread::get_id() << std::endl;
});
}
for (auto& w : workers_acout)
{
w.join();
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
With std::cout:
This is worker 1 in thread 139911511856896
This is worker This is worker 3 in thread 139911495071488
This is worker 4 in thread 139911486678784
2 in thread This is worker 5 in thread 139911503464192139911478286080
With acout():
This is worker 1 in thread 139911478286080
This is worker 2 in thread 139911486678784
This is worker 3 in thread 139911495071488
This is worker 4 in thread 139911503464192
This is worker 5 in thread 139911511856896
Run Code Online (Sandbox Code Playgroud)
既然如此C++20,你可以使用std::osyncstream包装器:
http://en.cppreference.com/w/cpp/io/basic_osyncstream
{
std::osyncstream bout(std::cout); // synchronized wrapper for std::cout
bout << "Hello, ";
bout << "World!";
bout << std::endl; // flush is noted, but not yet performed
bout << "and more!\n";
} // characters are transferred and std::cout is flushed
Run Code Online (Sandbox Code Playgroud)
它保证所有输出到同一个最终目标缓冲区(上例中的std :: cout)都没有数据争用,并且不会以任何方式交错或乱码,只要每次写入最后一个目标缓冲区是通过(可能是不同的)std :: basic_osyncstream实例生成的.
一个可行的解决方案是为每个线程使用行缓冲区。您可能会得到交错的行,但不会得到交错的字符。如果将其附加到线程本地存储,还可以避免锁争用问题。然后,当一行已满时(或者如果需要,则在刷新时),将其写入标准输出。最后一个操作当然必须使用锁。您将所有这些内容填充到流缓冲区中,并将其放在 std::cout 及其原始流缓冲区(又名装饰器模式)之间。
这无法解决的问题是格式标志(例如数字的十六进制/十进制/八进制)之类的问题,这些标志有时会在线程之间渗透,因为它们附加到流。假设您只是记录而不是将其用于重要数据,这没什么坏处。它有助于不专门格式化内容。如果您需要某些数字的十六进制输出,请尝试以下操作:
template<typename integer_type>
std::string hex(integer_type v)
{
/* Notes:
1. using showbase would still not show the 0x for a zero
2. using (v + 0) converts an unsigned char to a type
that is recognized as integer instead of as character */
std::stringstream s;
s << "0x" << std::setfill('0') << std::hex
<< std::setw(2 * sizeof v) << (v + 0);
return s.str();
}
Run Code Online (Sandbox Code Playgroud)
类似的方法也适用于其他格式。
为了快速调试 c++11 应用程序并避免交错输出,我只编写了这样的小函数:
...
#include <mutex>
...
mutex m_screen;
...
void msg(char const * const message);
...
void msg(char const * const message)
{
m_screen.lock();
cout << message << endl;
m_screen.unlock();
}
Run Code Online (Sandbox Code Playgroud)
我将这些类型的函数用于输出,如果需要数值,我只使用这样的东西:
void msgInt(char const * const message, int const &value);
...
void msgInt(char const * const message, int const &value)
{
m_screen.lock();
cout << message << " = " << value << endl;
m_screen.unlock();
}
Run Code Online (Sandbox Code Playgroud)
这很简单,对我来说效果很好,但我真的不知道它在技术上是否正确。所以我很高兴听到你的意见。
好吧,我没有读到这个:
我不想搜索每次出现的 std::cout 并添加一行锁定代码。
抱歉。不过我希望它可以帮助某人。