マルチスレッドと共有変数 - volatile?なにそれ。
複数のスレッドから共有する変数(典型的にはグローバル変数)を操作する際、どんな注意事項があるか?という話題です。プラットフォームはPOSIXを仮定します。pthreadのお話です。
まず、一口に「複数のスレッドで変数を共有」といっても、おおまかにいって次のような状況が考えられます。
- 読むスレッドしか存在しない
- 読むスレッド、書くスレッドの両方が存在する
順に見ていきましょう。観点は、「(1) volatile修飾が必要か」「(2) mutexによるロックが必要か」の2点です。
まず、「1. 読むスレッドしか存在しない」ケース。例えば、
static const char* gFoo = "immutable string literal";
なるグローバル変数を複数のスレッドが参照する場合です。この場合、(1)のvolatile修飾も、(2)のmutexも不要です。素のグローバル変数を好きに参照してかまいません。SUSv3(のRationale)にもその旨書いてあります。
(SUSv3より引用)
Access to read-only data need not be synchronized.
次、「2.1 書くスレッドは、read-modify-write動作を行う」の場合ですが、これは(2)のmutexによるロックが必要です。また、「2.2.2 書くスレッドは、read-modify-write動作を行わない - 変数の更新(メモリ操作)がアトミックに行えるケース」の場合も、(2)が必要です。この辺りは、あまり反論の出るところではないと思いますので説明は略します。
最後、「2.2.1 書くスレッドは、read-modify-write動作を行わない - 変数の更新(メモリ操作)がアトミックに行えるケース」の場合です。この場合、google様に聞いてみると「volatileをつけておけばそれで良い」と書いてあるサイトが多数ヒットします。
POSIXプラットフォームを使用している場合で、移植性に多少なりとも気を使いたいなら、この風習はやめたほうがよいです。理由は、
- マルチプロセッサ環境下で、あるプロセッサ上のスレッドが更新した変数の値が、別のスレッドで読めるかわからない
- 「変数の更新(メモリ操作)がアトミックに行える」かどうかはそれほど自明ではない。CPUやコンパイラの気分次第。少なくとも移植性はない
といったところですが、もっとも大きいのは、POSIX(SUSv3)がそういう、volatileでどうにかするという方法を認めていないという点です。
2.1, 2.2.1, 2.2.2, で mutex を使うべきである理由(および mutex を使えば充分であり、volatileが不要な理由) を次に述べます。
→続き