プログラミング言語C++のvolatile変数がスレッド間の同期機構として機能するか否かという論点について、有りそうな質問とその答えについての簡易メモ。(自身の思考整理用)
結論:このプログラムは可搬性が無く、未定義動作(undefined behavior)を引き起こす。C++11ではvolatile変数でなくatomic変数を用いるべき。C++03以前ではコンパイラ依存の仕組みを利用する。
volatile int running = 1; // 処理スレッド void another_thread() { while (running) { // ??? //... } } // 制御側スレッド void main_thread() { //... running = 0; // ??? }
- C++言語仕様ではどう定義される?(C++03以前)
- C++03以前のC++言語仕様ではそもそも「スレッド」を定義しない。つまりシングルスレッド動作を行うプログラムの意味しか定義しないため、マルチスレッドプログラムの意味論については完全に処理系(=コンパイラ)に依存する。
- C++言語仕様ではどう定義される?(C++11以降)
- 異なるスレッド間で順序付け(happens before)関係が定義されないメモリ読み書き操作*1は、データ競合(data race)による未定義動作を引き起こすと定義される。C++11におけるvolatile変数への読み書きは、異なるスレッド上で行われる処理の順序付けに影響を与えないため、言語仕様上は未定義動作を引き起こす。
- 環境XXだとvolatile変数で正しく動くが?
- 当該コンパイラのベンダ固有仕様もしくは単なる偶然によって、その環境ではプログラムが意図通り動作していると解釈される。C++11言語仕様としては未定義動作とされるため、異なるプラットフォーム/コンパイラベンダに移行した際のソースコード移植性は保証されない。
- それでもvolatile変数で十分なはずだ
- volatileとマルチスレッドにまつわる未定義動作は「全ての処理系において意図通り動作する保証がなされない」ことを意味する。現在利用している処理系では常に意図通り動作すると確認/判断しており、その結果で満足するならこれ以上言う事はない。
- volatile変数ではダメだという反例を挙げよ
- volatile変数への読み書きは不可分(atomic)操作であるとは保証されない。仮にレジスタサイズ2byte長のマシンを想定した場合、同環境では4byteサイズのvolatile変数アクセスはおそらく2回の2byteメモリアクセスに分割される。このとき異なるスレッドからvolatile変数へ読み/書きを同時に行うと、4byte領域のうち半分だけ更新された中間の値を読み込む可能性がある。つまり、プログラムの動作上は “どこにも存在しない値” を観測してしまうリスクがある。
- atomic変数ならば問題ないのか
- atomic変数に対する操作は、字義通り不可分な操作であるとC++11言語仕様によって保証される。前出の想定であれば、4byteサイズのatomic変数に対する読み/書きは不可分操作として実行され、他スレッドからは中間状態を観測しないと保証される。*2
- (番外)MSVCのvolatile変数は?
- Microsoft Visual C++ 2005以降はベンダ固有仕様の独自拡張が行われ、x86アーキテクチャ上のvolatile変数であればatomic変数がもつ一部の性質を有する*3。ただし、C++11におけるatomic操作の既定セマンティクス(memory_order_seq_cst)よりも “弱い” メモリ操作セマンティクスを持つことに注意。プログラムを意図通り動作させるために、ユースケースによっては追加のメモリフェンス命令が必要になる*4。
2019-04-17追記:Microsoft Visual C++ 2015以降は独自拡張への依存は推奨されない。コンパイラオプション /volatile:iso によりC++標準準拠の振舞いを指定し、マルチスレッド処理では素直にatomic変数を利用すること。
→ volatile変数とマルチスレッドとの関係についての押し問答(中編)に続く。
関連URL
- そろそろvolatileについて一言いっておくか - yamasaのネタ帳
- C++0x Memory Model 第1回 - 1.9 Program execution - Cry's Diary
- POS03-C. volatile を同期用プリミティブとして使用しない
- c++ - Volatile in C++11 - Stack Overflow
- (翻訳)良性データ競合へのC++的対応 - yohhoyの日記
- (翻訳)良性データ競合は有害である - yohhoyの日記
- volatile版atomic操作関数が存在する理由 - yohhoyの日記
*1:厳密には「同じメモリ位置に対する」「atomic操作ではない」「少なくとも一方が書き込み」操作を指す。
*2:C++11標準規格では、このatomic操作が「どのような仕組みで実現されるか」についてあまり規定しない。レジスタ2byte長で4byte長変数の不可分操作を実現する場合、内部にてミューテックスを用いたlockに基づく排他制御方式が考えられる。このためC++11標準ライブラリは、あるatomic操作が「lock-freeであるか否か」の問合せ機能を提供する。
*3:http://msdn.microsoft.com/en-us/library/12a04hfd%28v=vs.100%29.aspx
*4:http://msdn.microsoft.com/ja-jp/library/f20w0x5e.aspx http://msdn.microsoft.com/en-us/library/windows/desktop/ms684208.aspx