実用例
「スレッド間で共有する変数のアクセス権制御を C++ コンパイラで強制する方法」
http://developer.cybozu.co.jp/kazuho/2009/06/c-c79a.html
をupgrade_lockを利用して実装してみます。
これはmutexと保護対象オブジェクトを密結合させたオブジェクトを作る事で、ロック無しでアクセスする危険なコードを禁止することを目的としています。read lockを確保した場合にはconstでしか値を取れないためうっかり書き換える心配もありません。
ヘッダ
// rwsync.hpp #include <boost/thread.hpp> #include <boost/noncopyable.hpp> template <typename T> class rwsync : boost::noncopyable{ T m_obj; typedef boost::shared_mutex smutex; smutex lock; friend class read_ref; friend class upgrade_ref; friend class write_ref; public: class read_ref : public boost::noncopyable{ // read lock boost::shared_lock<smutex> m_lock; const rwsync<T>* const m_ref; public: read_ref(rwsync& mutex):m_lock(mutex.lock),m_ref(&mutex){} const T& operator*() const { return m_ref->m_obj; } const T* operator->() const { return &operator*(); } }; class write_ref; class upgrade_ref : public boost::noncopyable{ // upgrade lock friend class write_ref; boost::upgrade_lock<smutex> m_lock; rwsync<T>* const m_ref; public: upgrade_ref(rwsync& mutex):m_lock(mutex.lock),m_ref(&mutex){} const T& operator*() const { return m_ref->m_obj; } const T* operator->() const { return &operator*(); } }; class write_ref : public boost::noncopyable{ // unique lock boost::upgrade_to_unique_lock<smutex> m_lock; rwsync<T>* const m_ref; public: write_ref(upgrade_ref& lock):m_lock(lock.m_lock),m_ref(lock.m_ref){} T& operator*() { return m_ref->m_obj; } T* operator->() { return &operator*(); } const T& operator*() const { return m_ref->m_obj; } const T* operator->() const { return &operator*(); } }; };
このようなヘッダを用意しておくことで
// 使用例 #include "rwsync.hpp" rwsync<std::vector<int> > sync_vector; // 宣言はこんなに簡単! bool reader(){ rwsync<std::vector<int> >::read_ref vector_ref(sync_vector); // このようにロックを取ったオブジェクト経由でしかアクセスできない for(std::vector<int>::const_iterator iter = vector_ref->begin(); iter != vector_ref->end(); ++iter){ // 何か処理 } } bool writer(){ rwsync<std::vector<int> >::upgrade_ref vector_up_ref(sync_vector); if(vector_up_ref->front == 10){ // 配列の先頭が10なら rwsync<std::vector<int> >::write_ref vector_ref(vector_up_ref); vector_ref->push_back(20); } }
のように書けます
rwsyncから何らかのrefを通さない限りロックされているオブジェクトには触れられません。
また、read_refやupgrade_refからはconst付きのポインタ/参照しか得られないためオブジェクトに不用意に触れそうになったらコンパイラがエラーを吐いてくれます。
それを使ってハッシュマップを保護してみた例が以下に続きます。
unordered_mapでmemcached風のセマンティクスを実装します。
#include <boost/unordered_map.hpp> #include "rwsync.hpp" template <typename keytype, typename valuetype> class locked_map{ private: typedef boost::unordered_map<keytype,valuetype> storage; typedef rwsync<storage> synced_storage; typedef typename synced_storage::read_ref storage_read_ref; typedef typename synced_storage::upgrade_ref storage_upgrade_ref; typedef typename synced_storage::write_ref storage_write_ref; synced_storage map; public: locked_map(){} bool set(const keytype& key, const valuetype& value) // 保存。既にあるなら上書き。 { storage_upgrade_ref uplock(map); storage_write_ref wlock(uplock); wlock->erase(key); wlock->insert(std::make_pair(key,value)); return true; } bool append(const keytype& key, const valuetype& value) // 既にある場合に限り上書き。無ければ失敗 { storage_upgrade_ref uplock(map); const typename storage::const_iterator it = uplock->find(key); if(it != uplock->end()){ storage_write_ref wlock(uplock); wlock->erase(key); wlock->insert(std::make_pair(key,value)); return true; }else{ return false; } } bool add(const keytype& key, const valuetype& value) // 既に無い場合に限り保存。上書きは起こり得ない { storage_upgrade_ref uplock(map); const typename storage::const_iterator it = uplock->find(key); if(it == uplock->end()){ storage_write_ref wlock(uplock); wlock->insert(std::make_pair(key,value)); return true; } return false; } boost::optional<const valuetype> get(const keytype& key) // 検索。read lockだけで出来る { const storage_read_ref rlock(map); const typename storage::const_iterator it = rlock->find(key); if(it != rlock->end()){ return it->second; } else return NULL; } bool del(const keytype& key) // 削除。存在しなければfalseを返す { storage_upgrade_ref uplock(map); const typename storage::const_iterator it = uplock->find(key); if(it != uplock->end()){ storage_write_ref wlock(uplock); wlock->erase(key); return true; } return false; } };
memcachedはread-write lockを使っている気配が無いのでひょっとしたらこっちの方がスループットが出るかも知れません*1
まだ細々とチューニングはできそうですが今日はここまで。