Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Using cout in multiple threads might result in interleaved output.
So I tried to protect cout with a mutex.

The following code starts 10 background threads with std::async. When a thread starts, it prints "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.

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.

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)
like image 379
frankk Avatar asked May 27 '18 10:05

frankk


3 Answers

Your problem lies in the use of 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/

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.

This should fix your deadlock problem:

int value = f.get();
lock_guard<mutex> g(mtx);
cout << "Done thread" << value << "(" << this_thread::get_id() << ")" << endl;
like image 179
Pascal Kesseli Avatar answered Oct 07 '22 11:10

Pascal Kesseli


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. 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.

like image 41
Ulrich Eckhardt Avatar answered Oct 07 '22 12:10

Ulrich Eckhardt


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):

#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.

It might be not that simple in other cases, there are usually several threads, but it's all there.

like image 22
max630 Avatar answered Oct 07 '22 12:10

max630