■
Boostに以前からread-writeロックは実装されていたようですがバグがあったとかで最近の物ではupgrade_lock, upgrade_to_unique_lockにさし変わっています。
ただのロックと比べてパフォーマンスが出やすい上に素性の良い設計だと思うので紹介してみようと思います。
read lock
read-lockをする場合はshared_mutexを引数にshared_lock
#include <boost/thread.hpp> using namespace boost; shared_mutex mutex; void reader(){ shared_lock<shared_mutex> read_lock(mutex); // ここでロック! // クリティカルセクション }
スコープを外れると同時にshared_lockのデストラクタでアンロックされます。
write lock
write-lockする場合はshared_mutexに対して upgrade_lock → upgrade_to_unique_lockの順で取得してやる必要があります
#include <boost/thread.hpp> using namespace boost; shared_mutex mutex; void writer(){ upgrade_lock<shared_mutex> up_lock(mutex); upgrade_to_unique_lock write_lock(up_lock); // ここでロック! // クリティカルセクション }
upgrade_lockというのは「同時に一つしか取れない特別なread lock」と考える事ができます。
read lockが既に取られているかどうかに関わらずupgrade_lockはただ一つのスレッドだけが保持可能です。これによりwriter同士の衝突ポイントをreaderとは別の段階で解決する事ができ、read lock→write lockの途切れない昇格が可能となります。*1
同時に一つのスレッドからしか取れないread lockですが、その他のshared_lockを排他する事無くreaderを邪魔しません。
何のためにあるのかというと
#include <boost/thread.hpp> using namespace boost; shared_mutex mutex; void hoge(){ upgrade_lock<shared_mutex> up_lock(mutex); // 書き換える必要があるかどうか調べる if(/* 書き換える必要があるなら */){ upgrade_to_unique_lock write_lock(up_lock); // ここでロック! // readerを全て追い出した後のwrite処理 }else { // こっちの処理はreaderを排他せずに行える } }
のように書く事でread_lockと共存可能な利点を生かしスループットの向上が期待できます。
もし教科書的なread writeロックを用いて同様の目的を達成するなら
shared_mutex mutex; void hoge(){ read_lock rlock(mutex); // read lockを獲得 // 書き換える必要があるかどうか調べる if(/* 書き換える必要があるなら */){ rlock.unlock(); // デッドロックを避けるためにread lockを開放する必要がある write_lock wlock(mutex); // write lockを獲得 // 書き換える必要があるかどうか調べる(2度目! if(/* 書き換える必要があるなら */){ // やっと本命の処理 } } }
このようにread lockとwrite lock確保後にそれぞれチェックする書き方になります、書き換える必要性をチェックする処理が重い場合にはパフォーマンス低下を招きます。
2度チェックするのを避けるには、書き換える必要の無いときにもreaderを全て排斥するwrite lockを獲得するしかありません*2
upgrade_lock達の関係はすこしややこしいので
https://gist.github.com/22c650c292e94631bb84
このようなコードを書いて動作を試してみると良いかも知れません。