並列プログラミングの落とし穴③コード順序の入れ替え

最適化のためにプロセッサーは、実行中に命令の順序を入れ替えることがあり、並列プログラミングではこれも問題を引き起こします。

次のコードでは、全ての変数を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プラットフォーム移行のため再投稿]