Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 std::condition_variable: can we pass our lock directly to the notified thread?

I'm learning about C++11 concurrency, where my only prior experience with concurrency primitives was in Operating Systems class six years ago, so be gentle, if you can.

In C++11, we can write

std::mutex m;
std::condition_variable cv;
std::queue<int> q;

void producer_thread() {
    std::unique_lock<std::mutex> lock(m);
    q.push(42);
    cv.notify_one();
}

void consumer_thread() {
    std::unique_lock<std::mutex> lock(m);
    while (q.empty()) {
        cv.wait(lock);
    }
    q.pop();
}

This works fine, but I'm offended by the need to wrap cv.wait in a loop. The reason we need the loop is clear to me:

Consumer (inside wait())       Producer            Vulture

release the lock
sleep until notified
                               acquire the lock
                               I MADE YOU A COOKIE
                               notify Consumer
                               release the lock
                                                   acquire the lock
                                                   NOM NOM NOM
                                                   release the lock
acquire the lock
return from wait()
HEY WHERE'S MY COOKIE                              I EATED IT

Now, I believe one of the cool things about unique_lock is that we can pass it around, right? So it would be really elegant if we could do this instead:

Consumer (inside wait())       Producer

release the lock
sleep until notified
                               acquire the lock
                               I MADE YOU A COOKIE
                               notify and yield(passing the lock)
wake(receiving the lock)
return from wait()
YUM
release the lock

Now there's no way for the Vulture thread to swoop in, because the mutex remains locked all the way from I MADE YOU A COOKIE to YUM. Plus, if notify() requires that you pass a lock, that's a good way to ensure that people actually lock the mutex before calling notify() (see Signalling a condition variable (pthreads)).

I'm pretty sure that C++11 doesn't have any standard implementation of this idiom. What's the historical reason for that (is it just that pthreads didn't do it? and then why is that)? Is there a technical reason that an adventurous C++ coder couldn't implement this idiom in standard C++11, calling it perhaps my_better_condition_variable?

I also have a vague feeling that maybe I'm reinventing semaphores, but I don't remember enough from school to know if that's accurate or not.

like image 230
Quuxplusone Avatar asked Jun 15 '12 00:06

Quuxplusone


2 Answers

The ultimate answer is because pthreads didn't do it. C++ is a language that encapsulates operating system functionality. C++ is not an operating system or platform. And so it encapsulates the existing functionality of operating systems such as linux, unix and windows.

However pthreads also has a good rationale for this behavior as well. From the Open Group Base Specifications:

The effect is that more than one thread can return from its call to pthread_cond_wait() or pthread_cond_timedwait() as a result of one call to pthread_cond_signal(). This effect is called "spurious wakeup". Note that the situation is self-correcting in that the number of threads that are so awakened is finite; for example, the next thread to call pthread_cond_wait() after the sequence of events above blocks.

While this problem could be resolved, the loss of efficiency for a fringe condition that occurs only rarely is unacceptable, especially given that one has to check the predicate associated with a condition variable anyway. Correcting this problem would unnecessarily reduce the degree of concurrency in this basic building block for all higher-level synchronization operations.

An added benefit of allowing spurious wakeups is that applications are forced to code a predicate-testing-loop around the condition wait. This also makes the application tolerate superfluous condition broadcasts or signals on the same condition variable that may be coded in some other part of the application. The resulting applications are thus more robust. Therefore, IEEE Std 1003.1-2001 explicitly documents that spurious wakeups may occur.

So basically the claim is that you can build my_better_condition_variable on top of a pthreads condition variable (or std::condition_variable) fairly easily and without performance penalty. However if we put my_better_condition_variable at the base level, then those clients who did not need the functionality of my_better_condition_variable would have to pay for it anyway.

This philosophy of putting the fastest, most primitive design at the bottom of the stack, with the intent that better/slower things can be built on top of them, runs throughout the C++ lib. And where the C++ lib fails to follow this philosophy, clients are often (and rightly) irritated.

like image 69
Howard Hinnant Avatar answered Oct 07 '22 20:10

Howard Hinnant


If you don't want to write the loop you can use the overload that takes the predicate instead:

cv.wait(lock, [&q]{ return !q.is_empty(); });

It is defined to be equivalent to the loop, so it works just as the original code.

like image 30
R. Martinho Fernandes Avatar answered Oct 07 '22 21:10

R. Martinho Fernandes