2

I am a chip designer and we use mutex in our circuits all the time for atomic memory accesses. I am learning to code in CPP and I am having hard time understanding how the Mutex works in CPP.

I understand that any program is a process and threads under a process have their own local objects, in addition to global objects that are accessible from any thread under this process.

In my attempt to understand mutex more intimately, I found this example at http://www.cplusplus.com/reference/condition_variable/condition_variable/

1  // condition_variable example
2  #include <iostream>           // std::cout
3  #include <thread>             // std::thread
4  #include <mutex>              // std::mutex, std::unique_lock
5  #include <condition_variable> // std::condition_variable
6  
7  std::mutex mtx;
8  std::condition_variable cv;
9  bool ready = false;
10 
11 void print_id (int id) {
12   std::unique_lock<std::mutex> lck(mtx);
13   while (!ready) cv.wait(lck);
14   // ...
15   std::cout << "thread " << id << '\n';
16 }
17 
18 void go() {
19   std::unique_lock<std::mutex> lck(mtx);
20   ready = true;
21   cv.notify_all();
22 }
23 
24 int main ()
25 {
26   std::thread threads[10];
27   // spawn 10 threads:
28   for (int i=0; i<10; ++i)
29     threads[i] = std::thread(print_id,i);
30 
31   std::cout << "10 threads ready to race...\n";
32   go();                       // go!
33 
34   for (auto& th : threads) th.join();
35 
36   return 0;
37 }

Possible output (thread order may vary):

10 threads ready to race...
thread 2
thread 0
thread 9
thread 4
thread 6
thread 8
thread 7
thread 5
thread 3
thread 1

In line 28 and 29, 10 threads are scheduled. All of them are trying to lock same mutex (mtx). As I understand, one of the 10 thread will get the lock and will wait at line 13, while other 9 threads will try to get the lock and get blocked at line 12 itself.

Then comes the call to function go() at line 32. Go() also is trying to get the lock for the same mutex at line 19. But it should not get it as one print_id() thread is owning the lock. As per my understanding, this should result in a deadlock as go() is blocked at line 19, and can't get past that line, and consequently, can't send the cv.notify_all();

But the result in the link claims otherwise. It shows that go() effortlessly acquired the lock and sent the notify that in turn started the domino effect on 10 threads.

What am I missing? Is there some specific rule embedded in CPP specification that is allowing go() function to acquire the lock from the thread that was owning it? I have spent many hours in net searching for an answer, but in vain.

I will really appreciate some explanation of this phenomenon.

paul_brian
  • 31
  • 1
  • 3
  • There's a [link to the explanation](http://www.cplusplus.com/reference/condition_variable/condition_variable/wait/) on the page you linked to in the question. It doesn't take many hours to find. – molbdnilo Mar 30 '17 at 15:16
  • BTW, this is explicitly mentioned in the documentation, eg. [here](http://en.cppreference.com/w/cpp/thread/condition_variable/wait). – Useless Mar 30 '17 at 15:25
  • cv.wait unlocks mtx. – z32a7ul Mar 30 '17 at 15:32
  • Many thanks to all of you to point me to answer that was hanging in front of my nose. I actually read the cppreference.com page, but did not quite understand as clearly. But it is a shame that I did not follow link to "wait" in the page I picked the example from. A lesson for me... – paul_brian Mar 31 '17 at 04:51

2 Answers2

3

Condition variables unlock the lock they are passed internally during the wait, and relock it before they return.


The general pattern for a condition variable is a triplet of mutex, condition variable, and message.

The listener locks the mutex, then spins on the cv.wait(lck) until it detects a message. It leaves the .wait spin with the mutex locked, and can process the message (which could involve modifying it).

The sender locks the mutex, modifies the message, optionally unlocks the mutex, then does a cv.notify of some kind (depending on how many it wants to wake up).

While cv.wait(lck) the lck is sometimes unlocked, including when they are suspended waiting for a signal.

So what actually happens is that a bunch of threads all line up on the condition variable with the mutex unlocked.

The main thread then gets the lock, sets the message (ready=true), then does a .notify_all().

In practice, the main thread might set the message & .notify_all() before one of the listening threads manages to reach the cv.wait(lck). There is no problem, because if .notify_all() has been called, the ready=true was sequenced-before, so the listening thread will not .wait as the while(!ready) loop is while(false).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks a lot for the detailed explanation. I was trying to upvote your answer but it is not allowing me to. I don't understand why I can't give positive feedback. – paul_brian Mar 31 '17 at 04:54
1

cv.wait(lock) will release the lock while the condition variable is waiting, and it'll reacquire the lock (or block while trying to reacquire the lock) when notify_all is called.

Xirema
  • 19,889
  • 4
  • 32
  • 68
  • Thanks a lot. I was trying to upvote your answer but it is not allowing me to. I don't understand why I can't give positive feedback – paul_brian Mar 31 '17 at 04:55
  • @paul_brian It might be because your question was tagged as a duplicate of a different question. The expectation is that you'll either upvote one of the answers on the linked question, or else you'll edit your own post to explain why your issue is different from theirs, which would untag your post as a duplicate. – Xirema Mar 31 '17 at 04:57