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

Deadlock using std::mutex to protect cout in multiple threads

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

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

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

死锁的原因是什么?

当打印功能离开或 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)

您锁定互斥锁,然后等待其中一个未来,这反过来又需要锁定互斥锁本身。简单规则:不要等待锁定的互斥量。

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

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

Returns the value stored in the shared state (or throws its exception) when the shared state is ready.

If the shared state is not yet ready (i.e., the provider has not yet set its value or exception), the function blocks the calling thread and waits until it is ready.

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

因此,如果 future 后面的线程还没有到达 运行,函数将阻塞,直到该线程完成。但是,您在调用 future::get 之前就获得了互斥量的所有权,因此您正在等待的任何线程都无法为自己获得互斥量。

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

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

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

我编译并 运行 你的例子,然后在用 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(),后者又(通过强制结果)尝试再次获取锁

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