Condition condition_variable_any behavior when used with recursive_mutex?

When using condition_variable_any with recursive_mutex , will recursive_mutex publicly available from other threads while condition_variable_any::wait waiting? I am interested in both Boost and C ++ 11 versions.

In this case, I am mostly concerned:

 void bar(); boost::recursive_mutex mutex; boost::condition_variable_any condvar; void foo() { boost::lock_guard<boost::recursive_mutex> lock(mutex); // Ownership level is now one bar(); } void bar() { boost::unique_lock<boost::recursive_mutex> lock(mutex); // Ownership level is now two condvar.wait(lock); // Does this fully release the recursive mutex, // so that other threads may acquire it while we're waiting? // Will the recursive_mutex ownership level // be restored to two after waiting? } 
+4
source share
2 answers

From a rigorous interpretation of the Boost documentation, I came to the conclusion that condition_variable_any::wait usually does not cause recursive_mutex become available to other threads while waiting for a notification.

Class condition_variable_any

template<typename lock_type> void wait(lock_type& lock)

Effects:

Call lock.unlock() and block the current thread. The stream will be unblocked upon notification of a call to this->notify_one() or this->notify_all() , or false. When the thread is unlocked (for some reason), the lock is restored by calling lock.lock() before calling the wait for return. The lock is also reused by calling lock.lock() if the function exits with an exception.

So, condvar.wait(lock) will call lock.unlock , which in turn will call mutex.unlock , which reduces ownership by one (and not necessarily to zero).


I wrote a test program that confirms my conclusion (for Boost and C ++ 11):

 #include <iostream> #define USE_BOOST 1 #if USE_BOOST #include <boost/chrono.hpp> #include <boost/thread.hpp> #include <boost/thread/condition_variable.hpp> #include <boost/thread/locks.hpp> #include <boost/thread/recursive_mutex.hpp> namespace lib = boost; #else #include <chrono> #include <thread> #include <condition_variable> #include <mutex> namespace lib = std; #endif void bar(); lib::recursive_mutex mutex; lib::condition_variable_any condvar; int value = 0; void foo() { std::cout << "foo()\n"; lib::lock_guard<lib::recursive_mutex> lock(mutex); // Ownership level is now one bar(); } void bar() { std::cout << "bar()\n"; lib::unique_lock<lib::recursive_mutex> lock(mutex); // Ownership level is now two condvar.wait(lock); // Does this fully release the recursive mutex? std::cout << "value = " << value << "\n"; } void notifier() { std::cout << "notifier()\n"; lib::this_thread::sleep_for(lib::chrono::seconds(3)); std::cout << "after sleep\n"; // --- Program deadlocks here --- lib::lock_guard<lib::recursive_mutex> lock(mutex); value = 42; std::cout << "before notify_one\n"; condvar.notify_one(); } int main() { lib::thread t1(&foo); // This results in deadlock // lib::thread t1(&bar); // This doesn't result in deadlock lib::thread t2(&notifier); t1.join(); t2.join(); } 

Hope this helps someone who is facing the same dilemma when mixing condition_variable_any and recursive_mutex .

+4
source

You can fix this construct by adding the allowed_unlock_count parameter to each function that works with the mutex object; There are two types of guarantees that can be made with regard to allowed_unlock_count :

(enable-unlock-depth) allowed_unlock_count represents the depth of the allowed mutex unlock: the caller allows the bar unlock the mutexes allowed_unlock_count times. After this unlock, mutex status is not guaranteed.

(promise-unlock) allowed_unlock_count represents the mutex lock depth: the caller ensures that unlocking mutex exactly allowed_unlock_count times allows other threads to capture the mutex object.

These warranties are pre- and post-conditions of the functions.

Here bar depends on (promise-unlock) :

 // pre: mutex locking depth is allowed_unlock_count void bar(int allowed_unlock_count) { // mutex locking depth is allowed_unlock_count boost::unique_lock<boost::recursive_mutex> lock(mutex); // mutex locking depth is allowed_unlock_count+1 // you might want to turn theses loops // into an a special lock object! for (int i=0; i<allowed_unlock_count; ++i) mutex.unlock(); // mutex locking depth is 1 condvar.wait(lock); // other threads can grab mutex // mutex locking depth is 1 for (int i=0; i<allowed_unlock_count; ++i) mutex.lock(); // mutex locking depth is allowed_unlock_count+1 } // post: mutex locking depth is allowed_unlock_count 

The function being called must explicitly allow the caller to reduce the blocking depth.

+1
source

All Articles