C++

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

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