Search code examples
c++multithreadingdeadlockcondition-variable

Infinite waiting on condition variable


The simplified goal is to force calling 3 member functions in 3 different threads one by one (thread A calls F::first, thread B F::second, an thread C F::third).

In order to achieve the order for threads to be executed I used 1 condition variable and 2 bools indicating whether first and second threads finished their work.

In the code:

std::mutex mtx;
std::condition_variable cv;
bool firstPrinted = false;
bool secondPrinted = false;

class F {
public:    
    void first(std::function<void()> printFirst) {
        std::unique_lock<std::mutex> lck(mtx);
        std::cout << "first\n";
        printFirst();
        firstPrinted = true;
        cv.notify_one();
    }

    void second(std::function<void()> printSecond) {
        std::unique_lock<std::mutex> lck(mtx);
        std::cout << "second\n";
        cv.wait(lck, []() { return firstPrinted; });
        printSecond();
        secondPrinted = true;
        cv.notify_one();
    }

    void third(std::function<void()> printThird) {
        std::unique_lock<std::mutex> lck(mtx);
        std::cout << "third\n";
        cv.wait(lck, []() { return secondPrinted; });
        printThird();
    }
};

auto first = []() {
    std::cout << "1";
};
auto second = []() {
    std::cout << "2";
};
auto third = []() {
    std::cout << "3";
};
F f;
std::thread A(&F::first,  &f, first);
std::thread B(&F::second, &f, second);
std::thread C(&F::third,  &f, third);
A.join(); B.join(); C.join();

Now lets consider this situation:

Thread A does not start first - whether the first starting thread was B or C they both block (wait) until get notified (B blocks until notified by A, and C blocks until notified by B)

The infinite waiting (or perhaps deadlock !?) appears when the first starting thread is C, which always yields this output:

third
second
first
...and stalling here

Theoretically, this should not happen because calling cv.wait in thread C unlocks the mutex which allows thread B to run which in turn also waits (because condition didn't become true) and therefore it unlocks the locked mutex as well allowing thread A to start first which finally should enter critical section and notify B.

  1. What is the call path that causes stalling of the program ?

  2. What nuance did I miss ?

Please correct me if I was wrong in the thoughts above.


Solution

  • std::condition_variable::notify_one() will wake one of the threads waiting for the condition_variable. If multiple threads are waiting, one will be picked. It will wake, reacquire the lock check it's predicate. If that predicate is still false it will return to it's waiting state and the notification is in essence lost.

    That is what is happening here when the thread running first is the last to execute. When it reaches it's notify_one there will be two threads waiting for the condition_variable. If it notifies the thread running third, it's predicate will still return false. That thread will wake, fail it's predicate test and return to waiting. Your process now has no running threads and is frozen.

    The solution is to use std::condition_variable::notify_all(). This function wakes all waiting threads who will, one at a time, relock the mutex and check their own predicate.