「shared_lock」の編集履歴(バックアップ)一覧に戻る

shared_lock - (2010/12/25 (土) 18:06:41) のソース

*shared_lock
#right{last update: &update(format=Y/m/d (D))}

C++0x には boost::shared_mutex に相当するものが無さそう.(私が見逃してる可能性もある・・・)なので,「とりあえず使える」 shared_mutex もどきを実装してみた.速度とかはあまり考慮していない.

**依存ライブラリ
 #include <mutex>

**クラス宣言
 class shared_mutex
 {
 private:
     std::mutex _mutexForLock;
     std::mutex _mutexForLocking;
     unsigned int _shared_count;
 public:
     shared_mutex() : _shared_count(0) {}
     ~shared_mutex() {}
 
     shared_mutex(const shared_mutex&) = delete;
     shared_mutex& operator=(const shared_mutex&) = delete;
 
     void lock();
     bool try_lock();
     void unlock();
 
     void shared_lock();
     bool try_shared_lock();
 
     typedef std::mutex::native_handle_type native_handle_type;
     native_handle_type native_handle()
         { return _mutexForLock.native_handle(); }
 };
**メンバの実装と説明
***lock()
ミューテックスの排他的所有権(所謂書き込みロック)を取得する.所有権を取得するまで,カレントスレッドはブロックされる.
:throw|std::mutex::lock()の投げる例外.
 void lock()
     { _mutexForLock.lock(); }

***try_lock()
ブロッキングせずに排他的所有権の取得を試みる.
:return|ミューテックスの所有権を取得できた場合,true.そうでなければ false.
 bool try_lock()
     { return _mutexForLock.try_lock(); }

***unlock()
ミューテックスを解放する.
:throws|std::mutex::lock()の投げる例外.例外が投げられた場合,ロックは全て解除される.
 void unlock() {
     try _mutexForLocking.lock(); catch (...) {
         _mutexForLock.unlock();
         _shared_count  = 0;
         throw;
     }
     if (_shared_count == 0)
         _mutexForLock.unlock();
     else {
         _shared_count--;
         if (_shared_count == 0)
             _mutexForLock.unlock();
     }
     _mutexForLocking.unlock();
 }

***shared_lock()
ミューテックスの共有可能な所有権を取得する.
:throws|std::mutex::lock()の投げる例外.例外が投げられた場合,lockは行われない.
 void shared_lock() {
     _mutexForLocking.lock();
     if (_mutexForLock.try_lock()) {
         _shared_count++;
         _mutexForLocking.unlock();
     }
     else {
         if (_shared_count == 0) {
             _mutexForLocking.unlock();
             _mutexForLock.lock();
         }
         else {
             _shared_count++;
             _mutexForLocking.unlock();
         }
     }
 }

***try_shared_lock
ブロックせずにミューテックスの共有可能な所有権の取得を試みる.
:return|ミューテックスの所有権を取得できた場合,true.そうでなければ false.
 bool try_shared_lock() {
    try _mutexForLocking.lock(); catch (...) return false;
    if (_mutexForLock.try_lock()) {
         _shared_count++;
         _mutexForLocking.unlock();
         return true;
     }
     else {
         if (_shared_count == 0) {
             _mutexForLocking.unlock();
             return false;
         }
         else {
             _shared_count++;
             _mutexForLocking.unlock();
             return true;
         }
     }
 }

**解説
shared_lock とは,読み込みロック同士は同時にロックできるけれども,読み込みロックされている時には書き込みロックは取得できず,書き込みロックされてるときには読み込みロックは取得できない,というもの.

例として,W1 と R1, R2 の 3 つのスレッドがあるとする.スレッド W1 でlock()が呼ばれることを,W1.lock のように書くとして,様々な呼び出し順序で何が起こるかを考えてみる.
 W1.lock
 R1.shared_lock // ここでブロック
 W1.unlock      // ここで R1 のブロック解除

 R1.shared_lock
 W1.lock        // ここでもブロック
 R1.unlock      // ここで W1 のブロック解除

 R1.shared_lock
 R2.shared_lock // ここではブロックされない
 W1.lock        // ここでブロック
 R1.unlock      // ここではブロック解除されない
 R2.unlock      // 全ての共有ロックが解除されると,W1 のブロック解除
ここで仮想的なスレッド W2 というのを考えて,最初の共有ロックを W2.lock とみなし,最後の共有ロック解除を W2.unlock とみなせば,通常の mutex と同様の動作となることがわかる.従って,共有ロックの数をカウントしておき,それに従って 1 つの mutex を lock または unlock すれば良い.ここでは共有ロック数のカウントに _shared_count,mutex として _mutexForLock を使っている.

書き込みロックではカウンタは 0 のままなので,カウンタ==0 で現在のロックが書き込みなのか読み込みなのか判定できる.

少し難しいのは,共有数カウンタの一貫性を保つために _mutexForLocking でロック処理同士を排他制御する部分で,_mutexForLock との処理の順番を考慮しないとデッドロックに陥る.コツは,どちらかの mutex がブロックされているときにもう片方の mutex がロックされたままにならないようにすること.ここでは _mutexForLock がブロッキングされる部分(_mutexForLock.lock() の呼び出し)で,必ず _mutexForLockin が unlock された状態になるようにしている.

thread の最大数が UINT_MAX を超えることは想定していないが,通常は OS による制限が先に来る.同一スレッドで shared_lock を繰り返すような(明らかに異常な)処理を行うなら別だが,これは lock_guard 的な上位のアルゴリズムで回避するべきかと.

問題点としては, unlock が例外を投げてしまうのが気持ち悪い.これは _mutexForLockin.lock() が例外を投げるせいで,調べるとロックに失敗したときに発生しうるシステムエラーとある.どんなときに起こるのやら...例外時のモードも何がいいのか迷った.一応初期状態に戻しているが,何もしないほうがいいのかもしれない.

&trackback()
----
**参考
-[[Multi-threading Library for Standard C++ (Revision 1)>http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2497.html]]

----
**コメント
#comment(title_name=name,title_msg=comment)

----
**関連ページ
#related()

----
**関連ブログ
#blogsearch(C++0x)
記事メニュー
目安箱バナー