Search code examples
c++qtc++11condition-variableunique-lock

Why conditional_variable::notify_all may not wake up any thread?


I use conditional_variable::notify_all() to wake up a waiting thread(only one thread is waiting for the unique_lock indeed).

This code snippet works well at most time, but the log files(for details, see below) indicates that the parent thread could not aquire the unique_lock after the new created thread allready returned.

I would be grateful to have some help with this question.

Here is the related code snippet:

void MainWindow::deployAction(void)
{
    std::condition_variable cvRunOver;
    std::mutex mtxRunOver;
    std::unique_lock <std::mutex> ulkRunOver(mtxRunOver);
    QString workerThreadRes;
    std::thread workThread([&]()
    {
        workThread.detach();

        do_some_process_for_seconds();
        
        cvRunOver.notify_all();
        LOG(INFO)<<"to leave the subthread";
        google::FlushLogFiles(google::GLOG_INFO);
        return;
    });

    while (cvRunOver.wait_for(ulkRunOver, std::chrono::milliseconds(100)) == std::cv_status::timeout)
    {
        qApp->processEvents();
        auto curTim = std::chrono::steady_clock::now();
        std::chrono::duration<float> escapedTim= curTim-lastTim;
        if(std::chrono::duration_cast<std::chrono::seconds>(escapedTim).count()>=5)
        {
            LOG(INFO) << "processEvents()";
            google::FlushLogFiles(google::GLOG_INFO);
            lastTim = curTim;
        }
    }
    
    LOG(INFO) << "get lock and continue to run";
    google::FlushLogFiles(google::GLOG_INFO);
}

Here is the related log when the program does not work as experted:

Log line format: [IWEF]hh:mm:ss.uuuuuu threadid file:line] msg
20:19:14.638686 272568 mainwindow.cpp:208] to leave the subthread
20:19:17.669246 10256 mainwindow.cpp:221] processEvents()
20:19:22.678846 10256 mainwindow.cpp:221] processEvents()
20:19:17.669246 10256 mainwindow.cpp:221] processEvents()
20:19:22.678846 10256 mainwindow.cpp:221] processEvents()
20:19:17.669246 10256 mainwindow.cpp:221] processEvents()
20:19:22.678846 10256 mainwindow.cpp:221] processEvents()
20:19:17.669246 10256 mainwindow.cpp:221] processEvents()
...

Solution

  • You are misusing a condition variable. To use a condition variable:

    1. One thread must be notifying another thread about some change in shared state.

    2. There must actually be some shared state that has changed.

    3. The shared state must be protected by the mutex associated with the condition variable.

    4. The shared state must be tested before deciding to wait.

    5. The thread doing the signal or broadcast must have changed the shared state under the protection of the mutex prior to signaling or broadcasting.

    If you do not follow these four rules, your code will always fail. You don't seem to have any shared state protected by the mutex whose changes you are using the condition variable to notify another thread of. Without this, you cannot make a correct decision whether or not to wait and you will wind up waiting for something that has already happened.

    See this answer for more information.

    Imagine if you share a car with your sister. You ask your sister to ring a bell any time she brings the car back so you can stop waiting for it. Now imagine you want to use the car so you wait for the bell to ring. You will be waiting a long time if your sister wasn't using the car when you decided to wait!

    Your code has this flaw because your code decides to wait without first checking if the thing it's waiting for has already happened, violating rule 4. You also seem to be violating rule 3 because I don't see any shared state protected by the mutex. You may be violating rule 5 because I don't see your workThread changing any shared state prior to calling a notify function.

    I added some comments to example code from here to show how all the rules work:

        // condition_variable example
        #include <iostream>           // std::cout
        #include <thread>             // std::thread
        #include <mutex>              // std::mutex, std::unique_lock
        #include <condition_variable> // std::condition_variable
    
        std::mutex mtx;
        std::condition_variable cv;
        bool ready = false;
    
        void print_id (int id) {
          std::unique_lock<std::mutex> lck(mtx);
          while (!ready) cv.wait(lck); // rules 3 and 4 ("ready" is the shared state)
          // ...
          std::cout << "thread " << id << '\n';
        }
    
        void go() {
          std::unique_lock<std::mutex> lck(mtx); // rule 3
          ready = true; // rules 1 and 2
          cv.notify_all(); // rule 5
        }
    
        int main ()
        {
          std::thread threads[10];
          // spawn 10 threads:
          for (int i=0; i<10; ++i)
            threads[i] = std::thread(print_id,i);
    
          std::cout << "10 threads ready to race...\n";
          go();                       // go!
    
          for (auto& th : threads) th.join();
    
          return 0;
        }