squirrel_code @ ウィキ
shared_lock
最終更新:
squirrel_code
-
view
shared_lock
last update: 2010/12/25 (Sat)
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()