我什么时候应该使用 volatile/Thread.MemoryBarrier() 来保证线程安全?
4 回答
当您想跨线程访问变量而不锁定时,您可以使用volatile
/ 。Thread.MemoryBarrier()
原子变量,例如一个int
例子,总是被一次性读取和写入。这意味着在另一个线程更改它之前你永远不会得到一半的值,而在它改变之后你永远不会得到另一半。因此,您可以在不同线程中安全地读取和写入值而无需同步。
但是,编译器可能会优化掉一些您使用volatile
关键字阻止的读取和写入。例如,如果您有这样的循环:
sum = 0;
foreach (int value in list) {
sum += value;
}
sum
编译器实际上可能在处理器寄存器中进行计算,并且只在循环之后将值写入变量。如果您创建sum
变量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 主要用于无锁场景中,您试图避免锁定的性能损失。
编辑:您应该阅读Joe Duffy 撰写的关于 CLR 2.0 内存模型的这篇文章,它阐明了很多事情(如果您真的感兴趣,您应该阅读 Joe Duffie 的所有文章,他是。网)
顾名思义,易失性保证缓存值被刷新到内存中,以便所有线程看到相同的值。例如,如果我有一个整数,其最新写入保存在缓存中,其他线程可能看不到。他们甚至可能会看到该整数的缓存副本。将变量标记为易失性使其可以直接从内存中读取。
斯里万塔·斯里·阿拉文达·阿塔纳亚克