今までに見てきたスレッドにまつわるクラッシュ(落ちバグ)で、
特に多いのがクラス定義されたインスタンスのメソッドコールのタイミングです。
例えば、
std::queue等のメンバー変数を持つクラスのインスタンスに対して、
スレッド1からpushを行い
スレッド2からpopを行う
又は、
std::stringのメンバー変数を持つクラスのインスタンスに対して、
スレッド1から代入を行い
スレッド2からc_str()で参照する
これらは、タイミングが合えば、いとも簡単にアプリをクラッシュさせます。
小難しい内容はここでは省きますが、せめてこれだけはちゃんと意識しよう。
と願うのが、
メンバー変数に影響を与えるクラスメソッドは、ミューテックスで保護する
という事です。
例えば、以下のコードはすぐにクラッシュします
落ちる設計
class crashTestThread
{
public:
void pushThread(){
std::mt19937 mt;
while(1){
_queue.push( new int(mt()) );
usleep( 1 );
}
}
void popThread(){
while(1){
if( _queue.empty() ){
usleep( 1 );
}else{
const int* var = _queue.front();
_queue.pop();
delete var;
}
}
}
private:
std::queue<int*> _queue;
};
static crashTestThread s_crashTestThread;
static void testThread(){
auto t0 = std::thread( std::bind(&crashTestThread::pushThread, &s_crashTestThread) );
t0.detach();
auto t1 = std::thread( std::bind(&crashTestThread::popThread, &s_crashTestThread) );
t1.detach();
}
でもこのように、ミューテックスでメソッドコール前後を保護してあげると
少なくともクラッシュはしなくなります。
少なくともクラッシュしない設計
class crashTestThread
{
public:
void pushThread(){
std::mt19937 mt;
while(1){
_mutex.lock();
_queue.push( new int(mt()) );
_mutex.unlock();
usleep( 1 );
}
}
void popThread(){
while(1){
_mutex.lock();
const bool isEmpty = _queue.empty();
_mutex.unlock();
if( isEmpty ){
usleep( 1 );
}else{
_mutex.lock();
const int* var = _queue.front();
_queue.pop();
_mutex.unlock();
delete var;
}
}
}
private:
std::mutex _mutex;
std::queue<int*> _queue;
};