マルチスレッドと共有変数 - volatile?なにそれ。

複数のスレッドから共有する変数(典型的にはグローバル変数)を操作する際、どんな注意事項があるか?という話題です。プラットフォームはPOSIXを仮定します。pthreadのお話です。


まず、一口に「複数のスレッドで変数を共有」といっても、おおまかにいって次のような状況が考えられます。

  1. 読むスレッドしか存在しない
  2. 読むスレッド、書くスレッドの両方が存在する
    1. 書くスレッドは、read-modify-write動作を行う
    2. 書くスレッドは、read-modify-write動作を行わない
      1. 変数の更新(メモリ操作)がアトミックに行える*1
      2. 変数の更新(メモリ操作)がアトミックに行えない*2

順に見ていきましょう。観点は、「(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が不要な理由) を次に述べます。


続き

*1:適切にアラインした32bit変数など

*2:64bit変数や、ミスアラインした変数など