C++11標準ライブラリやBoost.Threadライブラリで提供されるcondition_variable_any
とrecursive_mutex
の組み合わせについてメモ。
まとめ:condition_variable_any
とrecursive_mutex
の組み合わせ利用は避けること。*1 *2
条件変数condition_variable_any
のwait
メンバ関数仕様では、指定されたロックオブジェクトに対して1度だけunlock操作を行う。同関数に対して2回以上lock操作されている(ロックカウント > 1)再帰ロックオブジェクトを指定した場合、該当ロックは解放されない(ロックカウント != 0)まま関数を呼び出したスレッドAはブロック状態に遷移する。この状況では別スレッドBでのロック獲得要求もブロックされるため、プログラム全体としてはデッドロックに陥る。
condition_variable_any cv; recursive_mutex mtx; typedef unique_lock<recursive_mutex> Lock; // スレッドA // 角括弧[]はロックカウント遷移を表す void threadA_waiter() { Lock lk1(mtx); // (1) 1回目のロック獲得[0→1] { //... Lock lk2(mtx); // (2) 2回目のロック獲得[1→2] while (/*...*/) { cv.wait(lk2); // (3) 1回だけロック解放[2→1] } } } // スレッドB void threadB_signal() { Lock lk(mtx); // (4) 別スレッドからロック獲得不可!! cv.notify_one(); }
N2447時点では明示的な事前条件が設定(下線部)されていたが、その後のWording整理により明記されなくなった*3。
void wait(Lock& lock);
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2447.htm
Precondition:lock
is locked by the current thread of execution. IfLock
supports recursive locking, the lock count is one. No other thread of execution is waiting on thiscondition_variable_any
object unlesslock
is, or refers to, the same underlying mutex object.
N3337 30.5.2/p10より引用。
template <class Lock> void wait(Lock& lock);
10 Effects:
- Atomically calls
lock.unlock()
and blocks on*this
.- When unblocked, calls
lock.lock()
(possibly blocking on the lock) and returns.- The function will unblock when signaled by a call to
notify_one()
, a call tonotify_all()
, or spuriously.- If the function exits via an exception,
lock.lock()
shall be called prior to exiting the function scope.
Boost.Thread 1.50.0ドキュメントより引用。
template<typename lock_type> void wait(lock_type& lock)
Effects:
Atomically calllock.unlock()
and blocks the current thread. The thread will unblock when notified by a call tothis->notify_one()
orthis->notify_all()
, or spuriously. When the thread is unblocked (for whatever reason), the lock is reacquired by invokinglock.lock()
before the call towait
returns. The lock is also reacquired by invokinglock.lock()
if the function exits with an exception.
関連URL
*1:仕様的には waitメンバ関数び呼び出し時にロックカウントが 1 であればプログラマの意図通り動作する。一方で多重ロック獲得操作を前提として recursive_mutex を利用しているはずであり、wait関数呼び出し前にロックカウント==1を保証可能ならば設計段階から通常 mutex の利用を検討すべきだろう。一般に condition_variable+mutex の組み合わせに対しては、condition_variable_any+任意ロックオブジェクトよりも効率的な実装が提供される(N3337 30.5/p1)。
*2:C++11標準ライブラリ範囲内で condition_variable_any を用いる合理的な理由として、timed_mutex との組み合わせ利用が挙げられる。
*3:N2497には "Condition variable wording for wait/timed_wait clarified." とのみあり詳細経緯は不明。