pthread の condition
SKK 辞書の初期化を非同期化して検索のテストしているのですが、辞書のロードが完了する前に検索が走る現象がちらほら出てきました。検索が空振りするのでテストも失敗します。対策が必要です。
検索時にコンテナが空かどうか確認するのはどうだろう。できれば、ロードが完了するまでそのまま待っていて欲しい。でも無限に待たれても困るので、せいぜい 3 秒くらいでタイムアウトして欲しい。こうなると、普通の mutex だけで実現するのは難しそうです。
pthread を調べてみると、まさにこういった目的のために condition という仕組みが用意されていました。とはいえ、pthread の API を生のまま叩くのは怖いので、いつものように薄いラッパーを書いてみました。
namespace pthread { //... class condition { mutex mutex_; pthread_cond_t handle_; condition(const condition&); condition& operator=(const condition&); void trylock() { assert(EBUSY == pthread_mutex_trylock(&mutex_.handle_) && "*** You MUST lock the pthread::condition object to avoid race conditions. ***"); } public: condition() { pthread_cond_init(&handle_, 0); } ~condition() { pthread_cond_destroy(&handle_); } operator mutex&() { return mutex_; } void signal() { trylock(); pthread_cond_signal(&handle_); } void broadcast() { trylock(); pthread_cond_broadcast(&handle_); } bool wait() { trylock(); return 0 == pthread_cond_wait(&handle_, &mutex_.handle_); } bool wait(long second, long nano_second = 0) { trylock(); timespec abstime; abstime.tv_sec = std::time(0) + second; abstime.tv_nsec = nano_second; return 0 == pthread_cond_timedwait(&handle_, &mutex_.handle_, &abstime); } }; // ... }
pthread のマニュアルを読むと、signal/broadcast/wait/timedwait の事前条件として、条件変数用の mutex がロックされていることを要求しているようです。となると、condition クラスは専用の mutex を保持するのが自然な気がします。とは言え、この mutex は condition クラスの外部からもロックできる必要があるので mutex 参照を返す変換演算子を用意しました。これは無理矢理な気もしますが、まあ、あまり深く考えないことにします。
pthread::condition を使った、典型的な生産者側のコードは以下の通り。
void produce(pthread::condtion& cond, object& obj) { pthread::lock scope(cond); // ... obj を更新する処理 cond.signal(); }
対する消費者側のコードはこんな感じになります。
void consume(pthread::condition& cond, object& obj) { pthread::lock scope(cond): if(obj.empty()) { cond.wait(); } // ... obj を消費する処理 }
案外すっきりと書けますね。lock は RAII を使った単純なラッパーです。
class suspend_cancel { void set(int state) { int tmp; pthread_setcancelstate(state, &tmp); } suspend_cancel(const suspend_cancel&); suspend_cancel& operator=(const suspend_cancel&); public: suspend_cancel() { set(PTHREAD_CANCEL_DISABLE); } ~suspend_cancel() { set(PTHREAD_CANCEL_ENABLE); } }; class lock { suspend_cancel shield_; mutex& target_; lock(); lock(const lock&); lock& operator=(const lock&); public: lock(mutex& target) : target_(target) { pthread_mutex_lock(&target_.handle_); } ~lock() { pthread_mutex_unlock(&target_.handle_); } };
lock が suspend_cancel クラスを保持しているのは、ロック保持中のスレッドがキャンセルされた場合でも RAII を保証するためです。
Boost.Thread について
最初は boost の Thread ライブラリも調べたのですが、肝心の condition クラスが意外とイマイチな印象です。wait 系のメソッド引数として述語が渡せるのは良いとしても、ScopedLock オブジェクトを渡す仕様は、間違った使い方を招きかねない気がします。素直に condition に mutex を持たせてはいけない理由があるのかなあ。問題を単純化しすぎているのだろうか。
単一の condition オブジェクトに対して複数の ScopedLock オブジェクトを適用することがあるか。いや、これは condition の排他制御にならないから NG ですね。複数の condition オブジェクトに対して、単一の ScopedLock オブジェクトを適用することがあるか。ううむ。アリかもしれない。無駄な待ちが発生しそうだけど。
なんだか釈然としないので、時間を見つけて ACE も見てみるつもりです。