「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)
表示オプション
横に並べて表示:
変化行の前後のみ表示: