Search code examples
c++c++20atomiccondition-variablestdatomic

std::atomic<bool>::wait vs. std::condition_variable::wait


I have a single producer, single consumer use case where the consumer blocks until the producer has made new data available. Here are two synchronization approaches that implement this:

New C++20 approach using std:atomic<bool>::wait

data d;
std::atomic<bool> ready = false;

void consume_bool() {
    ready.wait(false);
    do_consume(d);
    ready.store(false);
    ready.notify_one();
}

void produce_bool() {
    ready.wait(true);
    d = do_produce();
    ready.store(true);
    ready.notify_one();
}

Traditional C++17 approach using std::condition_variable

data d;
std::mutex m;
std::condition_variable consume_ready, produce_ready;
bool ready = false;

void consume_cv() {
    std::unique_lock lock{m};
    consume_ready.wait(m, [] { return ready; });
    do_consume(m);
    ready.store(false);
    produce_ready.notify_one();
}

void produce_cv() {
    std::unique_lock lock{m};
    produce_ready.wait(m, [] { return !ready; });
    d = do_produce();
    ready.store(true);
    consume_ready.notify_one();
}

Question

To me, it seems like the C++20 approach has entirely obsoleted the old approach. Is there a reason to still use traditional std::mutex and std::condition_variable synchronization?

Also, is waiting with std::atomic_bool as efficient (i.e. no busy-wait) as waiting for a std::condition_variable?


Solution

  • Maybe the question really is: Why where wait calls on atomics introduced?

    I found some potential hints about that in p0514r4:

    we also propose simpler atomic free functions that enable incremental change to pre-existing algorithms expressed in terms of atomics, to benefit from the same efficient support behind semaphores

    So this seems about 'fixing' the atomic API by adding a 'missing' synchronization feature. Another proposal (p0995r1) for adding wait to atomic_flag reads along the same lines:

    Our experience is that atomic_flag's interface is so minimal as to be mostly useless [...]

    We’ve heard of it being used as:

    • A questionable spinloop (as was originally intended);
    • A "check-in" flag used to know when at least one thread has reached a program location.

    Because users are (mis)using atomic flags for synchronizing threads, the API is enhanced to give them obvious better choices than spinning on the atomic.

    From experience it also seems to me that people just understand atomics really fast compared to the mutex + condition_variable + lock + semaphore + latch + barrier mess that the C++ standard library provides. Therefore enhancing that simple API with some useful functionality makes some sense to me.


    With that in mind I would answer your question as follows:

    The new atomic wait/notify API is just another synchronization API in C++. I would assume that implementators choosing the most efficient implementation will use the same underlying synchronization method in both cases.

    That beeing said, one big reason to use the std::mutex/std::condition_variable API could be, that it supports waits with timeout (wait_for/wait_until), whereas the atomic API does not.