简体   繁体   中英

C++ conditional wait race condition

Suppose that I have a program that has a worker-thread that squares number from a queue. The problem is that if the work is to light (takes to short time to do), the worker finishes the work and notifies the main thread before it have time to even has time to wait for the worker to finish.

My simple program looks as follows:

#include <atomic>
#include <condition_variable>
#include <queue>
#include <thread>

std::atomic<bool> should_end;

std::condition_variable work_to_do;
std::mutex work_to_do_lock;

std::condition_variable fn_done;
std::mutex fn_done_lock;
std::mutex data_lock;

std::queue<int> work;
std::vector<int> result;

void worker() {
    while(true) {
        if(should_end) return;

        data_lock.lock();
        if(work.size() > 0) {
            int front = work.front();
            work.pop();
            if (work.size() == 0){
                fn_done.notify_one();
            }
            data_lock.unlock();
            result.push_back(front * front);


        } else {
            data_lock.unlock();

            // nothing to do, so we just wait
            std::unique_lock<std::mutex> lck(work_to_do_lock);
            work_to_do.wait(lck);
        }
    }
}


int main() {

    should_end = false;
    std::thread t(worker); // start worker

    data_lock.lock();
    const int N = 10;
    for(int i = 0; i <= N; i++) {
        work.push(i);
    }
    data_lock.unlock();

    work_to_do.notify_one(); // notify the worker that there is work to do
    //if the worker is quick, it signals done here already
    std::unique_lock<std::mutex> lck(fn_done_lock);
    fn_done.wait(lck);

    for(auto elem : result) {
        printf("result = %d \n", elem);
    }

    work_to_do.notify_one(); //notify the worker so we can shut it down
    should_end = true;

    t.join();
    return 0;
}

Your try to use notification itself over conditional variable as a flag that job is done is fundamentally flawed. First and foremost std::conditional_variable can have spurious wakeups so it should not be done this way. You should use your queue size as an actual condition for end of work, check and modify it under the same mutex protected in all threads and use the same mutex lock for condition variable. Then you may use std::conditional_variable to wait until work is done but you do it after you check queue size and if work is done at the moment you do not go to wait at all. Otherwise you check queue size in a loop (because of spurious wakeups) and wait if it is still not empty or you use std::condition_variable::wait() with a predicate, that has the loop internally.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM