死锁使用 std::mutex 保护多线程中的 cout

Posted

技术标签:

【中文标题】死锁使用 std::mutex 保护多线程中的 cout【英文标题】:Deadlock using std::mutex to protect cout in multiple threads 【发布时间】:2018-11-06 03:26:54 【问题描述】:

在多个线程中使用 cout 可能会导致交错输出。 所以我尝试用互斥锁来保护 cout。

以下代码使用 std::async 启动 10 个后台线程。当一个线程启动时,它会打印“Started thread ...”。 主线程按照后台线程的创建顺序迭代它们的未来,并在相应线程完成时打印出“Done thread ...”。

输出已正确同步,但在一些线程启动和一些线程完成后(见下面的输出),发生死锁。所有后台线程都离开了,主线程正在等待互斥锁。

死锁的原因是什么?

当 print 函数离开或 for 循环的一次迭代结束时,lock_guard 应该解锁互斥锁,以便其中一个等待线程能够继续。

为什么所有的线程都饿死了?

代码

#include <future>
#include <iostream>
#include <vector>

using namespace std;
std::mutex mtx;           // mutex for critical section

int print_start(int i) 
   lock_guard<mutex> g(mtx);
   cout << "Started thread" << i << "(" << this_thread::get_id() << ") " << endl;
   return i;


int main() 
   vector<future<int>> futures;

   for (int i = 0; i < 10; ++i) 
      futures.push_back(async(print_start, i));
   

   //retrieve and print the value stored in the future
   for (auto &f : futures) 
      lock_guard<mutex> g(mtx);
      cout << "Done thread" << f.get() << "(" << this_thread::get_id() << ")" << endl;
   
   cin.get();
   return 0;

输出

Started thread0(352)
Started thread1(14944)
Started thread2(6404)
Started thread3(16884)
Done thread0(16024)
Done thread1(16024)
Done thread2(16024)
Done thread3(16024)

【问题讨论】:

在锁保护之前在变量中检索未来的结果f.get(); std::cout 是线程安全的,因此可以无锁地使用它并且仍然可以按预期输出:***.com/a/15034536/985296 相关,ostringstream 可能不会遇到这些问题。即ostringstream oss; oss &lt;&lt; "Done thread" &lt;&lt; f.get() &lt;&lt; "(" &lt;&lt; this_thread::get_id() &lt;&lt; ")" &lt;&lt; endl; cout &lt;&lt; oss.str();另见Is cout synchronized/thread-safe? 感谢您的宝贵回答。错误是在锁定互斥锁时使用 f.get() 。 ostringstream 版本也可以正常工作。 【参考方案1】:

您锁定互斥体,然后等待其中一个期货,而这又需要对互斥体本身进行锁定。简单规则:不要等待锁定的互斥锁。

顺便说一句:锁定输出流不是很有效,因为它很容易被您甚至无法控制的代码规避。与其使用这些全局变量,不如为需要输出某些东西(依赖注入)的代码提供一个流,然后以线程安全的方式从该流中收集数据。或者使用日志库,因为这可能就是您想要做的。

【讨论】:

【参考方案2】:

你的问题在于future::get的使用:

返回存储在共享状态中的值(或抛出其异常) 当共享状态准备好时。

如果共享状态尚未准备好(即提供者尚未 设置它的值或异常),函数阻塞调用线程 并等待它准备好。

http://www.cplusplus.com/reference/future/future/get/

因此,如果未来的线程尚未运行,则函数会阻塞,直到该线程完成。但是,您在调用 future::get 之前获得了互斥锁的所有权,因此您等待的任何线程都无法自己获得互斥锁。

这应该可以解决您的死锁问题:

int value = f.get();
lock_guard<mutex> g(mtx);
cout << "Done thread" << value << "(" << this_thread::get_id() << ")" << endl;

【讨论】:

【参考方案3】:

从源头上发现原因是好事。然而,经常发生的错误可能并不那么容易找到。原因也可能不同。幸运的是,如果发生死锁,您可以使用调试器进行调查。

我编译并运行了您的示例,然后在使用 gdb(gcc 4.9.2/Linux)附加到它之后,有一个回溯(跳过了嘈杂的实现细节):

#0  __lll_lock_wait ()
...
#5  0x0000000000403140 in std::lock_guard<std::mutex>::lock_guard (
    this=0x7ffe74903320, __m=...) at /usr/include/c++/4.9/mutex:377
#6  0x0000000000402147 in print_start (i=0) at so_deadlock.cc:9
...
#23 0x0000000000409e69 in ....::_M_complete_async() (this=0xdd4020)
    at /usr/include/c++/4.9/future:1498
#24 0x0000000000402af2 in std::__future_base::_State_baseV2::wait (
    this=0xdd4020) at /usr/include/c++/4.9/future:321
#25 0x0000000000404713 in std::__basic_future<int>::_M_get_result (
    this=0xdd47e0) at /usr/include/c++/4.9/future:621
#26 0x0000000000403c48 in std::future<int>::get (this=0xdd47e0)
    at /usr/include/c++/4.9/future:700
#27 0x000000000040229b in main () at so_deadlock.cc:24

这正是其他答案中解释的内容 - 锁定部分 (so_deadlock.cc:24) 中的代码调用 future::get(),而后者又(通过强制结果)尝试再次获取锁。

在其他情况下可能没那么简单,通常有几个线程,但都在那里。

【讨论】:

以上是关于死锁使用 std::mutex 保护多线程中的 cout的主要内容,如果未能解决你的问题,请参考以下文章

C++11:基于std::queue和std::mutex构建一个线程安全的队列

C11线程管理:互斥锁

[C++11 多线程同步] --- 互斥锁

[C++11 多线程同步] --- 互斥锁

[C++11 多线程同步] --- 互斥锁

c++11多线程---线程锁(mutex)