|
■No65877 (howling さん) に返信
> てっきり最適化で入れ替えが行われているのかと、もろに勘違いしてました。
> うーん、ここらへんになってくると、そういう物もあるのか…くらいな認識でいた方が良いのでしょうか。
> 実際に動かしてみて、デバッグする際に「この変数おかしいぞ?」となると思うのですが、
> その可能性の1つとして、こういった物がある、という程度という意味です。
同期を取るのにロックを必ず使うならば雑学程度の扱いでもよいかと思いますが、
アトミック操作をフルに使ってロックフリーにしようと思うならどういうときにどう
おかしくなるかを把握しておかなければハマると思います。
C#で、メモリ操作の順が期待と異なってしまう例を書いてみました。あまり自信ありませんが。
Windows 7 x64, Visual Studio 2012, .Net Framework 4.5, Any CPU,
実CPU: Opteron 6376 で、UseBarrier = falseにすると
[何かヘン!] のコードが実行され、UseBarrier = trueにすると実行されない
(数分間チェックしただけですが)ことを確認できました。
# C#でvolatile使ったことがないので使い方を間違えてるかも。
public class Program
{
// ふたつのスレッドのロックに使うフラグたち。
private static volatile bool[] Locks = { false, false };
// ごく一瞬を除いてほとんどの期間 -1。
private static volatile int Value = -1;
// true: メモリバリアを使う。false: メモリバリアを使わない。
private static bool UseBarrier = false;
public static void Main(string[] args)
{
List<Thread> threads = new List<Thread>();
for (int i = 0; i < 2; i++)
{
// 引数 = 0でThreadBodyを実行するスレッドと
// 引数 = 1でThreadBodyを実行するスレッドを
// ひとつずつ作る。
Thread thread = new Thread(ThreadBody);
thread.Start(i);
threads.Add(thread);
}
foreach (Thread thread in threads)
{
// ここで待ってても終わらないので確認が終わったら強制終了して下さい。
thread.Join();
}
}
private static void ThreadBody(object id)
{
Console.WriteLine("Thread #{0} started.", id);
for (; ; )
{
DoSomething((int)id);
}
}
private static void DoSomething(int id)
{
Locks[id] = true;
if (UseBarrier) Thread.MemoryBarrier();
// ここのwhile条件を抜ける瞬間はLocks[自ID] == true
// かつLocks[他ID] == false。
// つまり、メモリ読み書きが機械語の順に実行されているなら
// 他スレッドは同時にはこの判定を行えないので、以下しばらくの
// 範囲は同時に実行されることがない。
while (Locks[id ^ 1])
{
Locks[id] = false;
if (UseBarrier) Thread.MemoryBarrier();
Locks[id] = true;
if (UseBarrier) Thread.MemoryBarrier();
}
// 同時に1スレッドしか実行されないはずの範囲 -- ここから
int tmp = Value;
if (tmp != -1)
{
// [何かヘン!]
// ここに入る条件を満たすには、他スレッドが(★)の部分にいなければならないが、
// メモリ操作の追い越しがなければそんなことにはならないはず。
Console.WriteLine("Thread #{0}: Value = {1}, myLock = {2}, anotherLock = {3}",
id, tmp, Locks[id], Locks[id ^ 1]);
}
Value = id;
if (UseBarrier) Thread.MemoryBarrier();
// (★) Valueが-1以外の値を取るのはこの部分だけ。
Value = -1;
if (UseBarrier) Thread.MemoryBarrier();
// 同時に1スレッドしか実行されないはずの範囲 -- ここまで
Locks[id] = false;
if (UseBarrier) Thread.MemoryBarrier();
}
}
<余談>
上記コード、以前GCC向けのCで書いたコードを、余計な部分を削りつつC#に移植したものです。
元コードはこちら。
http://slashdot.jp/journal/544783
</余談>
|