並列プログラミングの落とし穴③コード順序の入れ替え
最適化のためにプロセッサーは、実行中に命令の順序を入れ替えることがあり、並列プログラミングではこれも問題を引き起こします。
次のコードでは、全ての変数を0で初期化し、タスクAではs_xに「1」を代入してからs_yaにs_yを代入します。タスクBではs_yに「1」を代入してからs_xaにs_xを代入します。タスクAとタスクBを同時実行させますが、タスクAが先に実行されればs_xaが「1」のはずですし、タスクBが先に実行されればs_yaが「1」のはずです。また、同時に実行されればどちらも「1」となります。
AとBのタスクが終了したあとで、両方が「0」になっていることはありえないように思われます。
class Program
{
internal static volatile int s_x;
internal static volatile int s_xa;
internal static volatile int s_y;
internal static volatile int s_ya;
static void Main(string[] args)
{
while (true)
{
s_x = 0;
s_xa = 0;
s_y = 0;
s_ya = 0;
Task A = Task.Factory.StartNew(() =>
{
s_x = 1;
s_ya = s_y;
});
Task B = Task.Factory.StartNew(() =>
{
s_y = 1;
s_xa = s_x;
});
Task.WaitAll(A, B);
Debug.Assert(s_xa == 1 || s_ya == 1);
}
}
}
ところが、実行してみるとアサートが発生します! この理由は、読み込みが書き込みより前に実行されるように、プロセッサーがこのコードの順序を自由に入れ替えることがあるからです。
これを回避するにはThread.MemoryBarrier()を2つの代入命令の間に挟んで、プロセッサーによる命令の並べ替えを明示的に禁止しなければなりません。
[MSDN Blogプラットフォーム移行のため再投稿]