Do I need to synchronize std::condition_variable/condition_variable_any::notify_one
Asked Answered
H

2

15

Do I need to synchronize std::condition_variable/condition_variable_any::notify_one?

As far as I can see, if lost of notifications is acceptable - it is OK to call notify_one not protected (by mutex for example).

For instance, I saw following usage patterns (sorry, don't remember where):

{
    {
        lock_guard<mutex> l(m);
        // do work
    }
    c.notify_one();
}

But, I inspected libstdc++ sources, and I see:

condition_variable::notify_one

void condition_variable::notify_one() noexcept
{
    int __e = __gthread_cond_signal(&_M_cond);
    // XXX not in spec
    // EINVAL
    if (__e)
        __throw_system_error(__e);
}

and condition_variable_any::notify_one:

void condition_variable_any::notify_one() noexcept
{
    lock_guard<mutex> __lock(_M_mutex);
    _M_cond.notify_one();
}

And here is layout of condition_variable_any:

class condition_variable_any
{
    condition_variable _M_cond;
    mutex _M_mutex;
    // data end

I.e. it is just thin wrapper around condition_variable+mutex.

So, questions:

  1. Is it thread-safe to not protect notify_one by mutex for either condition_variable_any or condition_variable?
  2. Why implementation of condition_variable_any uses additional mutex?
  3. Why implementation of condition_variable_any::notify_one and condition_variable::notify_one differs? Maybe condition_variable::notify_one requires manual protection but condition_variable_any::notify_one doesn't? Is it libstdc++ bug?
Hospice answered 8/4, 2013 at 19:30 Comment(0)
O
14

I.e. it is just thin wrapper around condition_variable+mutex.

Er, no. Just because it has members of those types doesn't make it a thin wrapper. Try to understand what it actually does, not just the types of its private members. There's some quite subtle code there.

  1. Is it thread-safe to not protect notify_one by mutex for either condition_variable_any or condition_variable?

Yes.

In fact, calling notify_one() with the mutex locked will cause waiting threads to wake up, attempt to lock the mutex, find it is still locked by the notifying thread, and go back to sleep until the mutex is released.

If you call notify_one() without the mutex locked then the waking threads can run immediately.

2 Why implementation of condition_variable_any uses additional mutex?

condition_variable_any can be used with any Lockable type, not just std:mutex, but internally the one in libstdc++ uses a condition_variable, which can only be used with std::mutex, so it has an internal std::mutex object too.

So the condition_variable_any works with two mutexes, the external one supplied by the user and the internal one used by the implementation.

3 Why implementation of condition_variable_any::notify_one and condition_variable::notify_one differs? Maybe condition_variable::notify_one requires manual protection but condition_variable_any::notify_one doesn't? Is it libstdc++ bug?

No, it's not a bug.

The standard requires that calling wait(mx) must atomically unlock mx and sleep. libstdc++ uses the internal mutex to provide that guarantee of atomicity. The internal mutex must be locked to avoid missed notifications if other threads are just about to wait on the condition_variable_any.

Ocasio answered 9/4, 2013 at 16:29 Comment(5)
Thanks! That helped me a lot. Espeically answer to 2 and 3 - good point about atomicity, and inner stuff that uses mutex anyway. By the way, you may add link to pthread_cond_wait pubs.opengroup.org/onlinepubs/7908799/xsh/… to show that most common implementation works only with it's own mutex in internals. Could you please clarify what exactly do you mean on "there's some quite subtle code there.".Hospice
Regarding 1. - can non-locked condition_variable::notify_one lead to missed notifications? I.e. thread #1 does job under mutex, send result, unlock mutex, [meanwhile] thread #2 locks mutex but not yet called wait, [meanwhile] thread #1 calls notify_one, [meanwhile] thread #2 calls wait - notification is lost.Hospice
That's not a lost notification, that's the waiting thread not checking the condition predicate before waiting. Locking the mutex doesn't help that situation, the notification could still come before the waiting thread locks the mutex. You must check the associated predicate when waiting on a condition variableOcasio
even with predicate checking leak is possible: while(pred()) { /*meanwhile notify_one can happen here*/ cond.wait(m); } - notification is lost. (with 1-arg wait)Hospice
@qble, only if your program is broken. If the predicate is false when you check it, how can it become true before unlocking the mutex?Ocasio
A
2

(1) I don't see any reason that signalling a condition variable has to be guarded by a mutex, from a data-race stand-point. Obviously, you have the possibility of receiving redundant notifications or losing notifications, but if this is an acceptable or recoverable error condition for your program, I don't believe there's anything in the standard that will make it illegal. The standard, of course, won't guard you against race conditions; it's the programmer's responsibility to make sure that race conditions are benign. (And, of course, it is essential that the programmer not put any "data races", which are defined very specifically in the standard but don't apply directly to synchronization primitives, or undefined behavior is summoned.)

(2) I can't answer a question like this about the internal implementation of a standard library facility. It is, of course, the vendor's responsibility to provide library facilities that work correctly and meet the specification. This library's implementation may have some internal state that requires mutual exclusion to avoid corruption, or it may perform locking in order to avoid lost or redundant notifications. (Just because your program may tolerate them, doesn't mean arbitrary users of the library can, and in general I expect they can't.) It would just be speculation on my part what they're guarding with this mutex.

(3) condition_variable_any is made to work on any lock-like object, while condition_variable is designed specifically to work with unique_lock<mutex>. The latter is probably easier to implement and/or more performant than the former, since it knows specifically which types it is operating on and what they require (whether they're trivial, whether they fit in a cache line, whether they map directly to a specific platform's set of syscalls, what fences or cache coherence guarantees they imply, etc.), while the former provides a generic facility for operating on lock-ish objects without being stuck specifically with the constraints of std::mutex or std::unique_lock<>.

Affect answered 8/4, 2013 at 20:26 Comment(3)
Perhaps I should add to Q: implementation of condition_variable_any is just thin wrapper around condition_variable+mutex.Hospice
"Just because your program may tolerate them, doesn't mean arbitrary users of the library can, and in general I expect they can't" - I think you don't get my point - if I don't guard my notify_one by mutex by myself - there can be lost notifications, but if I guard it - they should be impossible. Implementation should not care about lost notifications when user don't protects notify_one by mutex.Hospice
@qble: Again, I can't really comment on what a standard library vendor chooses to do to implement their implementation of the standard. They're allowed to use whatever compiler magic to get it to work (including undefined behavior, if they happen to know that their platform defines it) and they can take advantage of very specific knowledge. It could be that they know that std::lock_guard<std::mutex> acts atomically enough for them, but the types associated with the template parameters may not. You might find a clue in their implementation of wait().Affect

© 2022 - 2024 — McMap. All rights reserved.