ThreadとMutexオブジェクトの生成順序
Boostにはthreadを扱うテンプレートライブラリが用意されていて,比較的簡単にスレッドの生成などが行える.クラスのメンバ関数をスレッドとして実行するには,以下のようにboost::threadのコンストラクタにboost::bindを渡してやれば良い.
#include <boost/bind.hpp> #include <boost/thread.hpp> class my_thread { public: my_thread(); void run(); private: boost::thread m_thread; }; void my_thread::run() { } my_thread::my_thread() : m_thread(boost::bind(&my_thread::run, this)) { }
これでmy_threadクラスのrun()関数がスレッドとして動作するわけだけれど,実際にスレッドを使うには,mutexやcondition variableなどを利用するのが普通である.従って,以下のようにmy_threadクラスにmutexやcondition variableを追加すれば良いと考えるが,以下のコードは注1の行付近でレースコンディションが発生する.
/* * 間 違 っ た コ ー ド */ #include <boost/bind.hpp> #include <boost/thread.hpp> #include <boost/thread/condition.hpp> class my_thread { public: my_thread(); void run(); private: boost::thread m_thread; boost::mutex m_mutex; boost::condition m_condition; }; void my_thread::run() { boost::mutex::scoped_lock lock(m_mutex); /**** 注1 ****/ m_condition.wait(lock); /* * 何らかの処理 */ } my_thread::my_thread() : m_thread(boost::bind(&my_thread::run, this)) { }
これはなぜかというと,メンバ変数の宣言がm_thread, m_mutex, m_conditionとなっており,m_mutexやm_condition変数が初期化される前にスレッドが生成されているため,注1の行に到達した時点で初期化していないm_conditionやm_mutexを利用してしまう可能性がある.可能性があると書いたのは,実際に上記のようなコードを書いても問題なく動く場合が多い.たいていうまく動くが,時々失敗するというバグは大体レースコンディションに起因するものが多くてデバッグが厄介なのが常である.というわけで,このコードを正しく書きなおすと以下のようになる.
/* * 正 し い コ ー ド */ #include <boost/bind.hpp> #include <boost/thread.hpp> #include <boost/thread/condition.hpp> class my_thread { public: my_thread(); void run(); private: boost::mutex m_mutex; boost::condition m_condition; boost::thread m_thread; /**** スレッドを最後に生成 ****/ }; void my_thread::run() { boost::mutex::scoped_lock lock(m_mutex); m_condition.wait(lock); /* * 何らかの処理 */ } my_thread::my_thread() : m_thread(boost::bind(&my_thread::run, this)) { }