6

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?

7
  • Does this help? Commented Jan 16, 2024 at 15:15
  • 1
    @JakobStark yeah, it explains the difference in principle, but it's still unclear to me whether one approach really obsoletes the other. Commented Jan 16, 2024 at 15:18
  • It seems to me that "single producer, single consumer use case" is a rather well-defined situation, where there aren't really any options to consider. If it works, it works, and you don't need the old approach. But at the end of your question, it looks like you are asking in general, and not just in your use-case. Clarify? Commented Jan 16, 2024 at 15:39
  • @anatolyg if waiting through std::atomic_bool::wait is somehow inferior to waiting on std::condition_variable, then that'd be a reason to use the old version, at least in some cases. It's a general question, but the point is to float ideas of what could be used to justify the old version in this particular example. Commented Jan 16, 2024 at 15:43
  • Old approach does not require any atomics because condition is protected by mutex lock. And it is still handy if condition is more complex than just a boolean flag. Commented Jan 16, 2024 at 15:56

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.