yohhoyの日記

技術的メモをしていきたい日記

unlock操作とデストラクト処理のスキマ

C++11標準ライブラリのミューテックスクラスと、unlock操作〜オブジェクト破棄で起こり得る挙動についてメモ。

注意ミューテックスクラスの内部実装に対する要件の話であり、標準ライブラリ利用者にとっては “意図通り” 動作することが保証されている。(言い換えると、ミューテックスを自作する場合には考慮すべき事項)

スレッドセーフな参照カウンタを内包するクラスXを考える。ミューテックスX::mは参照カウンタ変数X::cの保護用。また関数release_objectでは参照カウンタを操作し、参照カウントが 0 となった時にオブジェクトを破棄する。(サンプルコード片はN3018より引用)

struct X {
  //...
  std::mutex m;  // X::cアクセス保護
  int        c;  // 参照カウンタ
};

void release_object(X *o)
{
  o->m.lock();                 // (1)
  bool del = (--(o->c) == 0);  // (2)
  o->m.unlock();               // (3)
  if (del) { delete o; }       // (4)
}

2つのスレッドA, Bのみが同一Xオブジェクトの所有権を保持しており(X::c==2)、同時にrelease_object関数を呼び出す。ここでは下記順序にて処理が行われたとする。

  1. A:(1) m.lockメンバ関数呼び出しによるロック獲得。
  2. B:(1) m.lockメンバ関数呼び出し。ロック獲得失敗のためブロックされる。
  3. A:(2) 参照カウンタをデクリメントし(2→1)、変数delにfalseを代入。
  4. A:(3) m.lockメンバ関数呼び出しによるロック解放。ただしスレッドAはまだm.lockメンバ関数内を処理中とする。
  5. B:(1) ロック獲得に成功し(Step2の続き)、m.lockメンバ関数呼び出しからreturn。
  6. B:(2) 参照カウンタをデクリメントし(1→0)、変数delにtrueを代入。
  7. B:(3) m.lockメンバ関数呼び出しによるロック解放。
  8. B:(4) 変数delがtrueのため、オブジェクトoのデストラクト処理。メンバ変数o::mも破棄される。
  9. A:(3) m.lockメンバ関数内の残り処理(Step4の続き)を実行し、関数呼び出しからreturn。このとき既にミューテックスmは破棄済み!
  10. A:(4) 変数delはfalseのため何もしない。

C++11標準規格は、上記シナリオでも正常動作することを処理系に要求する(30.4.1.2.1/p2)。

N3337 30.4.1.2/p5(一部), 30.4.1.2.1/p2より引用。

5 (snip) [Note: Construction and destruction of an object of a mutex type need not be thread-safe; other synchronization should be used to ensure that mutex objects are initialized and visible to other threads. -- end note]

2 [Note: After a thread A has called unlock(), releasing a mutex, it is possible for another thread B to lock the same mutex, observe that it is no longer in use, unlock it, and destroy it, before thread A appears to have returned from its unlock call. Implementations are required to handle such scenarios correctly, as long as thread A doesn't access the mutex after the unlock call returns. These cases typically occur when a reference-counted object contains a mutex that is used to protect the reference count. -- end note]

関連URL