简体   繁体   English

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

[英]Deadlock using std::mutex to protect cout in multiple threads

Using cout in multiple threads might result in interleaved output. 在多个线程中使用cout可能会导致交错输出。
So I tried to protect cout with a mutex. 所以我试图用互斥锁来保护cout。

The following code starts 10 background threads with std::async. 以下代码使用std :: async启动10个后台线程。 When a thread starts, it prints "Started thread ...". 线程启动时,会打印“Started thread ...”。 The main thread iterates over the futures of the background threads in the order in which they were created and prints out "Done thread ..." when the corresponding thread finished. 主线程按照创建顺序迭代后台线程的未来,并在相应的线程完成时打印出“Done thread ...”。

The output is synchronized correctly, but after some threads have started and some have finished (see output below), a deadlock occurres. 输出正确同步,但在某些线程启动后有些线程已经完成(参见下面的输出),就会出现死锁。 All background threads left and the main thread are waiting for the mutex. 剩下所有后台线程,主线程正在等待互斥锁。

What is the reason for the deadlock? 僵局的原因是什么?

When the print function is left or one iteration of the for loop ends, the lock_guard should unlock the mutex, so that one of the waiting threads would be able to proceed. 当保留打印功能或for循环的一次迭代结束时,lock_guard应解锁互斥锁,以便其中一个等待线程能够继续。

Why are all the threads left starving? 为什么所有线程都挨饿?

Code

#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;
}

Output 产量

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

Your problem lies in the use of future::get : 你的问题在于使用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 (ie, 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/ http://www.cplusplus.com/reference/future/future/get/

So if the thread behind the future didn't get to run yet, the function blocks until that thread finishes. 因此,如果未来的线程尚未运行,则该函数将阻塞,直到该线程结束。 However, you take ownership of the mutex before calling future::get , so whichever thread you're waiting for will not be able to attain the mutex for itself. 但是,在调用future::get之前,您将future::get互斥锁的所有权,因此您正在等待的任何线程都无法为自己获取互斥锁。

This should fix your deadlock problem: 这应该可以解决您的死锁问题:

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

You lock the mutex and then wait for one of the futures, which in turn requires a lock on the mutex itself. 您锁定互斥锁,然后等待其中一个期货,这反过来需要锁定互斥锁本身。 Simple rule: Don't wait with locked mutexes. 简单规则:不要等待锁定的互斥锁。

BTW: Locking output streams is not very effective, because it can easily be circumvented by code you don't even control. BTW:锁定输出流不是很有效,因为它甚至无法控制的代码很容易被绕过。 Rather than using those globals, give a stream to code that needs to output something (dependency injection) and then collect the data from that stream in a threadsafe way. 不是使用那些全局变量,而是为需要输出内容的代码(依赖注入)提供流,然后以线程安全的方式从该流中收集数据。 Or use a logging library, because that's probably what you wanted to do anyway. 或者使用日志库,因为这可能是你想要做的。

It is good that the reason was spotted from the source. 从源头上发现原因很好。 However, quite often the error, as it happens, may be not so easy to locate. 然而,通常情况下,错误可能不容易找到。 And the reason may differ as well. 原因可能也不同。 Fortunately, in case of deadlock you can use debugger to investigate it. 幸运的是,在死锁的情况下,您可以使用调试器来调查它。

I compiled and ran your example, then after attaching to it with gdb (gcc 4.9.2/Linux), there is a backtrace (noisy implementation details skipped): 我编译并运行了你的例子,然后在用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

This is just what is explained in the other answers - the code in locked section (so_deadlock.cc:24) calls future::get(), which in turn (by forcing the result) trying to acquire the lock again. 这就是其他答案中解释的内容 - 锁定部分中的代码(so_deadlock.cc:24)调用future :: get(),然后(通过强制结果)尝试再次获取锁定。

It might be not that simple in other cases, there are usually several threads, but it's all there. 在其他情况下可能不是那么简单,通常有几个线程,但它就在那里。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM