スレッドセーフのためにvolatile/Thread.MemoryBarrier()を使用する必要があるのはいつですか?
4 に答える
ロックせずにスレッド間で変数にアクセスする場合はvolatile
/を使用します。Thread.MemoryBarrier()
たとえば、アトミックな変数は、int
常に一度に読み書きされます。つまり、別のスレッドが変更する前に値の半分を取得し、変更後に残りの半分を取得することはありません。そのため、同期せずに異なるスレッドで値を安全に読み書きできます。
ただし、コンパイラは一部の読み取りと書き込みを最適化して除外する場合がありますが、これはvolatile
キーワードで防止できます。たとえば、次のようなループがある場合:
sum = 0;
foreach (int value in list) {
sum += value;
}
コンパイラは実際にはプロセッサ レジスタで計算を行いsum
、ループの後で変数に値を書き込むだけです。sum
variableを作成するvolatile
と、コンパイラは変更ごとに変数を読み書きするコードを生成するため、ループ全体で値が最新になります。
どうしたの
private static readonly object syncObj = new object();
private static int counter;
public static int NextValue()
{
lock (syncObj)
{
return counter++;
}
}
?
これにより、必要なすべてのロック、メモリバリアなどが行われます。volatile
およびに基づくカスタム同期コードよりもよく理解され、読みやすくなっていますThread.MemoryBarrier()
。
編集
volatile
orを使用するシナリオが思い浮かびませんThread.MemoryBarrier()
。例えば
private static volatile int counter;
public static int NextValue()
{
return counter++;
}
上記のコードと同等ではなく、スレッドセーフではありません (魔法volatile
の++
ようにスレッドセーフになることはありません)。
このような場合:
private static volatile bool done;
void Thread1()
{
while (!done)
{
// do work
}
}
void Thread2()
{
// do work
done = true;
}
(動作するはずです) Thread2 が完了したら、ManualResetEventを使用して通知します。
基本的に、コードをスレッドセーフにするために他の種類の同期を使用している場合は、その必要はありません。
ほとんどのロック メカニズム (ロックを含む) は自動的にメモリ バリアを意味するため、複数のプロセッサが正しい情報を取得できます。
Volatile と MemoryBarrier は、ほとんどの場合、ロックによるパフォーマンスの低下を回避しようとするロックフリーのシナリオで使用されます。
編集: CLR 2.0 メモリ モデルに関する Joe Duffy によるこの記事を読む必要があります。 。ネット)
名前が意味するように、volatile はキャッシュ値がメモリにフラッシュされることを保証し、すべてのスレッドが同じ値を参照できるようにします。たとえば、最新の書き込みがキャッシュに保存されている整数がある場合、他のスレッドはそれを認識しない場合があります。その整数のキャッシュ コピーが表示される場合もあります。変数を揮発性としてマークすると、メモリから直接読み取られるようになります。
スリワンタ スリ アラヴィンダ アタナヤケ