4

I have a std::condition_variable_any that waits on a custom lock which is a composition of two mutexes (one std::mutex and one shared-locked std::shared_mutex). Its unlock() operation simply unlocks both mutexes sequentially.

For example (pseudocode):

mutex mutex1;
shared_mutex mutex2;
condition_variable_any cv;
// acquiring the locks
DualLock lock(unique_lock(mutex1), shared_lock(mutex2));
// waiting
cv.wait(lock);

cv.wait() should atomically unlock both mutex1 and mutex2, and put the thread to sleep until cv gets notified.

It it still guaranteed that the thread is sleeping and listening to the condition variable notification, once any of mutex1 or mutex2 is unlocked?

Or is it possible that one mutex gets unlocked, and a second thread locks it, sends the notification, but this first thread was not yet sleeping and listening. So the notification never arrived and no wakeup occurs.

tmlen
  • 8,533
  • 5
  • 31
  • 84
  • If `DualLock` does not use `std::lock` to lock the mutexes, you are in danger of deadlock. Your pseudocode looks like you are in danger of deadlock in the line constructing the `DualLock`. – Howard Hinnant Dec 23 '16 at 20:38

1 Answers1

3

If DualLock meets the requirements of BasicLockable, then the code will perform as hoped. If not, it won't.

BasicLockable reference is here

So the notification never arrived and no wakeup occurs.

This can never happen when condition variables are used properly. The wakeup of a condition variable cannot be interpreted as a signal of an event, since the documentation of condition_variable explains that there can be spurious wakeups.

At best, the notification is an indication that now may be a good time to test the condition you're waiting for (and you may do so in the secure knowledge that the test is protected by the mutex).

The state of the condition and the state of the blocked-ness of the current thread are two separate concerns.

Here's a better example (assumes that DualLock models BasicLockable correctly):

bool condition_met = false;

mutex mutex1;
shared_mutex mutex2;
condition_variable_any cv;
// acquiring the locks
DualLock lock(unique_lock(mutex1), shared_lock(mutex2));
while(!condition_met)
{
    // waiting
    cv.wait(lock);
}
// whatever happens, you have the lock here

// ...work

lock.unlock();

Your notifier would notify this way:

DualLock lock(unique_lock(mutex1), shared_lock(mutex2));
condition_met = true;
lock.unlock();     // note the order: unlock mutex first...
cv.notify_all();   // ...then notify the condition variable
alter_igel
  • 6,899
  • 3
  • 21
  • 40
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • So if `cv.notify_all()` gets called while `cv.wait() -> DualLock::unlock()` is still being executed on the waiting thread (after it has only unlocked one of the two mutexes), it is still guaranteed that `wait` will not start to sleep, but instead immediatly return? – tmlen Dec 23 '16 at 19:17
  • @tmlen the lock method of your custom mutex must behave as per the concept (I.e. Either fully lock the mutexes, block on them, or throw an exception). If you get that right, the above code will always work. The complication here is with the implementation of your dual lock, not with the cv. – Richard Hodges Dec 23 '16 at 19:33