C++11標準ライブラリとBoost.Threadライブラリ(Boost 1.48.0)に含まれる、threadオブジェクトのデストラクタの振る舞いと例外安全に関するメモ。その1, その2 の続き。
2020-12-02追記:C++2a(C++20)標準ライブラリでは、デストラクタで自動的にjoinを呼び出すstd::jthread
が追加される。std::thread
動作はC++11時点と同一。
2013-02-05追記:Boost.Thread 1.50.0〜1.56.0では記事内容に関する破壊的変更が行われる。id:yohhoy:20120206 も参照のこと。
問題のまとめ
- ローカル変数の参照/ポインタを新スレッドへ渡した後に、例外送出などで該当関数から抜けると、
boost::thread
デストラクタにて意図しないスレッドdetachが行われる*1。 - その結果、別スレッドの実行中に渡されたオブジェクト生存期間が終了してしまい、ダングリング参照/ポインタへの書き込み操作によりスタックメモリ領域が破壊される。
- さらに、メモリ破壊は別スレッドから不定タイミングで行われるため、再現性のない原因特定困難なバグとなる。
この問題に対しC++11標準ライブラリでは、プログラマが明示的にスレッドjoinまたはdetachを行わない限り、std::thread
デストラクタはstd::terminate
を呼び出してプログラムを終了させる。この動作仕様により、少なくとも問題箇所を早期に発見できる。
対策のまとめ
- (可能なら)
boost::thread
ではなくstd::thread
を使う。 - 別スレッドにローカル変数の参照/ポインタを渡さない。別スレッドへの引数渡しは値のコピーまたはムーブを用い、別スレッドからの処理結果取得にはfuture*2を用いる。
- 別スレッドにローカル変数の参照/ポインタを渡す場合は、RAIIイディオムを用いてスレッドjoinが行われることを保証する。
関連URL