Search code examples
c++c++11conditional-statementsmutexrace-condition

How to decrese the possibility of the producer acquiring the lock whereas the consumer could not get the lock when using std::condition_variable?


How can I decrease the possibility of the producer(i.e. main thread in the code snippet code below) acquiring the lock whereas the consumer(i.e. wait thread) could not get the lock? It is would be better if you could tell me a method to avoid it. I don't think it is a good idea to use std::thread::sleep_for or std::thread::yield. And I have done some test and found that there is no effect when using std::thread::yield.

I have thought about it for a long time, I would be grateful to have some help with this question.

If you run the code snippet, you may see such output:

Waiting... 
test 
Notifying falsely... 
Notifying true change... 
Notifying true change... 
Notifying true change... 
Notifying true change... 
Notifying true change... 
Notifying true change...
(**many many such output**)
Notifying true change... 
test 
...finished waiting. i == 1

Here is the related code snippet(check https://godbolt.org/z/9dwDJN, quoted from en.cppreference.com/w/cpp/thread/condition_variable/notify_one ):

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{std::cout<<"test"<<std::endl; return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying falsely...\n";
    cv.notify_one(); // waiting thread is notified with i == 0. 
    // cv.wait wakes up, checks i, and goes back to waiting 

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done)
    {
        std::cout << "Notifying true change...\n";
        lk.unlock();
        cv.notify_one(); // waiting thread is notified with i == 1, cv.wait returns 
        //std::this_thread::sleep_for(std::chrono::seconds(1));   // I don't think it is good method.
        //std::this_thread::yield();  //Maybe, this does not work.
        lk.lock();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join();
    t2.join();
}

Solution

  • You can wait for done completely without a lock, if you make it an atomic variable. This would even make much more sense to me in this case than using a mutex. However, this does not change the whole concept of busy waiting, that is, spinning in a loop until done is set.

    Alternatively, you can wait for done to be set without blocking a CPU core. Simply use the same concept of condition variables. You can even use the same condition variable that was used for synchronization of i. The demo of this approach is here: https://en.cppreference.com/w/cpp/thread/condition_variable.

    The question is which of these two solutions is "better". I would likely prefer the first spin-based solution, since one can expect, that the waiting will be very short (if the system is not oversubscribed etc.).