「shared_lock」の編集履歴(バックアップ)一覧はこちら

shared_lock」(2010/12/25 (土) 18:06:41) の最新版変更点

追加された行は緑色になります。

削除された行は赤色になります。

*shared_lock 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)
*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)

表示オプション

横に並べて表示:
変化行の前後のみ表示:
記事メニュー
目安箱バナー