Edited at

安全なスレッドを実装する為に、これだけは意識したい事

More than 3 years have passed since last update.

今までに見てきたスレッドにまつわるクラッシュ(落ちバグ)で、

特に多いのがクラス定義されたインスタンスのメソッドコールのタイミングです。

例えば、


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;
};