並列プログラミングの落とし穴②データ分裂
前回のカウンタープログラムでは共有データのインクリメントがアトミックではないために発生した落とし穴でした。では単純な読み・書きだけなら問題ないでしょうか?
32ビットOSの下では、64ビット変数の読み・書きはアトミックではないので、問題が発生します(コンパイラーによって64ビットOSの下でもアトミックでない場合があります)。次のコードを見てください(Visual Studio 2010 でコンソールプロジェクトを作成し、名前空間 System.Threading.Tasks と System.Diagnosticsを追加)。
class Program
{
internal static ulong s_x;
static void Main(string[] args)
{
Task A = Task.Factory.StartNew(() =>
{
int i = 0;
while (true)
{
s_x = (i & 1) == 0 ? 0x0L : 0xaaaabbbbccccddddL;
i++;
}
});
Task B = Task.Factory.StartNew(() =>
{
while (true)
{
ulong x = s_x;
Debug.Assert
(x == 0x0L || x == 0xaaaabbbbccccddddL,
"データ分裂!"+ x.ToString("x16"));
}
});
Task.WaitAll(A,B);
}
}
一方のタスクで ulong(64ビット符号なし整数)型の変数 s_x に 0x0L か 0xaaaabbbbccccddddLを書き込み、もう一方のタスクで、いずれかの値が正しく書き込まれているかどうかをチェックします。
64ビット版のWindows 7上でビルド実行したとき、VS2010 では、ビルド プラットフォームを x64 にするとアサートは発生しませんが、x86 にするとアサートが発生します。
アサートを見ると、タスクAでは0x0Lと0xaaaabbbbccccddddL以外は書き込んでいないのに、タスクBの変数 xに0x00000000ccccddddLが代入されているのが分かります。これが並列プログラミングにおける共有データのデータ分裂の落とし穴です。