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()

参考



コメント

name
comment


関連ページ



関連ブログ

#blogsearch
記事メニュー
人気記事ランキング
目安箱バナー