Question

Condition variables are generally used such that the state they refer to is modified under a mutex. However, when the state is just a single set-only flag, there's no need for a mutex to prevent simultaneous execution. So one might want to do something like this:

flag = 1;
pthread_cond_broadcast(&cvar);

However, this is only safe if pthread_cond_broadcast implies a write memory barrier; otherwise, the waiting thread may see the condition variable broadcast before the flag write. That is, the waiting thread may awaken, consume the cvar signal, but see the flag still 0.

So, my question is: Do the pthread_cond_broadcast and pthread_cond_signal calls imply a write memory barrier? If so, where is this specified in the relevant POSIX (or other) specifications? The spec seemed unclear on this point.

Note: I am aware that, in practice, this does result in a memory barrier (on Linux, because thread awakening implies a full CPU memory barrier, and the cross-library function call implies a compiler memory barrier). However, I'm interested here in what the spec guarentees.

Was it helpful?

Solution

Regardless of whether it implies a memory barrier, the code is still not correct. Consider the read side:

while (flag == 0)
    pthread_cond_wait(&cvar, &mutex);

If the read side is suspended between testing flag == 0 and executing the wait, the write side can execute the flag = 1; pthread_cond_signal(&cvar);. The read side will then miss the wakeup entirely - it will wait forever. Remember that wakeups are not queued - if there's no waiter when the condition variable is signalled, the signal has no effect. To avoid this, the write side needs to lock the mutex anyway.

OTHER TIPS

Under POSIX, if you write to a variable from one thread and read it from another then you must protect it with a mutex. There is no exception made for pthread_cond_broadcast.

If your platform/compiler offer atomic variables then they may make additional guarantees about those. For example, if flag is a C++11 std::atomic<int> then this code is OK.

The compiler is entitled to assume that non volatile objects do not have their value spuriously changed. It's essentially to be able to assume that for even the most simple CSE optimization to be valid (and it makes the optimization undetectable).

It is a fundamental invariant and the basis of any local reasoning on mutable state.

Such modification of even types that have atomic loads and stores at the CPU level may work for non/limited optimized compilations and fail at higher optimization when the compiler is allowed to analyse the program to infer stuff.

So: don't do it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top