You misunderstand what your code does.
Your code in line // 1 free not to block at all. condition_variables can (and will!) have side awakenings - they can wake up without a good reason.
You are responsible for verifying whether the awakening is false.
There are 3 things required with condition_variable :
- A
condition_variable - A
mutex - Some data protected by
mutex
mutex protected data is modified (under mutex ). Then (with mutex possibly disabled), condition_variable notified.
At the other end, you will block mutex , and then wait for the condition variable. When you wake up, your mutex will be blocked, and you will check if the wakeup is false by looking at the data protected by mutex . If this is a valid awakening, you process and continue.
If this is an invalid awakening, you return to waiting.
In your case, you do not have protected data, you cannot distinguish false awakenings from real ones, and your design is incomplete.
It is not surprising that with an incomplete design, you don’t see the reason mutex restarts: it is blocked, so you can safely check the data to make sure that the awakening was false or not.
If you want to know why condition variables are designed in this way, probably because this project is more efficient than “reliable” (for some reason), and instead of exposing higher-level primitives, C ++ showed more low level more efficient primitives.
Creating a higher level abstraction on top of this is not difficult, but there are design solutions. Here is one std::experimental::optional built on top:
template<class T> struct data_passer { std::experimental::optional<T> data; bool abort_flag = false; std::mutex guard; std::condition_variable signal; void send( T t ) { { std::unique_lock<std::mutex> _(guard); data = std::move(t); } signal.notify_one(); } void abort() { { std::unique_lock<std::mutex> _(guard); abort_flag = true; } signal.notify_all(); } std::experimental::optional<T> get() { std::unique_lock<std::mutex> _(guard); signal.wait( _, [this]()->bool{ return data || abort_flag; }); if (abort_flag) return {}; T retval = std::move(*data); data = {}; return retval; } };
Now each send can lead to success of get on the other end. If more than one send is encountered, only one of them is consumed by get . If and when abort_flag set, instead of get() , {} returned immediately;
The above is supported by several consumers and manufacturers.
An example of the use of the above can be a preview state source (for example, a user interface stream) and one or more rendering renderers (which are not fast enough to run in a user interface stream).
Preview state uploads the preview state to data_passer<preview_state> willy-nilly. Players compete, and one of them captures it. Then they display it and pass it back (through some mechanism).
If the preview states arrive faster than the renderers consume them, only the last of them is interesting, so the previous ones are discarded. But existing previews are not interrupted just because a new state appears.
Questions that are asked below the conditions of the race.
If the transmitted data is atomic , we can not do without a mutex on the sending side?
So something like this:
template<class T> struct data_passer { std::atomic<std::experimental::optional<T>> data; std::atomic<bool> abort_flag = false; std::mutex guard; std::condition_variable signal; void send( T t ) { data = std::move(t); // 1a signal.notify_one(); // 1b } void abort() { abort_flag = true; // 1a signal.notify_all(); // 1b } std::experimental::optional<T> get() { std::unique_lock<std::mutex> _(guard); // 2a signal.wait( _, [this]()->bool{ // 2b return data.load() || abort_flag.load(); // 2c }); if (abort_flag.load()) return {}; T retval = std::move(*data.load()); // data = std::experimental::nullopt; // doesn't make sense return retval; } };
the above does not work.
Let's start by listening. He performs step 2a, then waits (2b). It evaluates the condition in step 2c, but has not yet returned from lambda.
The broadcast stream then performs step 1a (data setting), then signals a condition variable. At the moment, no one is waiting for a condition variable (the code in lambda is not counted!).
Then the listening thread ends the lambda and returns a false wake. Then it locks the condition variable and never notices that the data has been sent.
std::mutex , used when waiting for a condition variable, should protect the write to the data "passed" by the condition variable (no matter what test you do to determine if the wakeup is false) and read (in lambda), or there is a possibility "lost signals." (At least in a simple implementation: more complex implementations can create loose paths for “ordinary cases” and use only mutex in double checking. This is beyond the scope of this question.)
The use of atomic variables does not get around this problem, because the two operations “determine whether the message was false” and “overwrite in the condition variable” should be atomic with respect to the “falsity” of the message.