要为多线程应用程序实现锁定免费代码,我使用了volatile
个变量,
理论上:volatile
关键字仅用于确保所有线程都能看到volatile变量的最新值;因此,如果线程A
更新变量值并且线程B
在更新发生之后读取该变量,它将看到最近从线程A写入的最新值。
正如我在一本坚果壳中的 C#4.0 一书中所读到的那样
这是不正确,因为
应用volatile不会阻止写入后读取交换。
可以通过在每次获取Thread.MemoryBarrier()
变量之前添加volatile
来解决此问题,如:
private volatile bool _foo = false;
private void A()
{
//…
Thread.MemoryBarrier();
if (_foo)
{
//do somthing
}
}
private void B()
{
//…
_foo = true;
//…
}
如果这解决了问题;考虑我们有一个while循环,它依赖于其中一个条件的值;在while循环之前放置Thread.MemoryBarrier()
是解决问题的正确方法吗?例如:
private void A()
{
Thread.MemoryBarrier();
while (_someOtherConditions && _foo)
{
// do somthing.
}
}
为了更准确,我希望_foo
变量在任何时候任何线程要求它时给出最新的值;因此,如果在调用变量之前插入Thread.MemoryBarrier()
修复了问题,那么我可以使用Foo
属性而不是_foo
并在该属性的get中执行Thread.MemoryBarrier()
,如:
Foo
{
get
{
Thread.MemoryBarrier();
return _foo;
}
set
{
_foo = value;
}
}
答案 0 :(得分:9)
“C#In a Nutshell”是正确的,但它的说法没有实际意义。为什么?
让我们澄清一下。拿你的原始代码:
private void A()
{
//…
if (_foo)
{
//do something
}
}
如果线程调度程序已经检查了_foo
变量,但在//do something
注释之前它被暂停,会发生什么?好吧,那时你的另一个线程可能会改变_foo
的值,这意味着你所有的挥发性物品和Thread.MemoryBarriers都没有计算!如果do_something
的值为false,则绝对必须避免_foo
,那么您别无选择,只能使用锁定。
但是,如果突然do something
变为假,_foo
可以执行,那么这意味着volatile关键字足以满足您的需求。
要明确:告诉您使用记忆障碍的所有响应者都不正确或提供过度杀伤。
答案 1 :(得分:5)
这本书正确 CLR的内存模型表明可以重新排序加载和存储操作。这适用于易失性和非易失性变量。
将变量声明为volatile
仅意味着加载操作将具有获取语义,而存储操作将具有 release 语义。此外,编译器将避免执行某些优化,这些优化继续以序列化的单线程方式访问变量(例如,从循环中提升加载/存储)。
单独使用volatile
关键字不会创建关键部分,也不会导致线程彼此神奇地同步。
当您编写无锁代码时,您应该非常小心。没有什么简单的事情,即使是专家也很难做到这一点 无论你想要解决的是什么原始问题,都可能有更合理的方法来实现它。
答案 2 :(得分:0)
在第二个示例中,您还需要在循环中放置Thread.MemoryBarrier();
,以确保每次检查循环条件时都获得最新值。
答案 3 :(得分:0)
从here ...
拉出来class Foo
{
int _answer;
bool _complete;
void A()
{
_answer = 123;
Thread.MemoryBarrier(); // Barrier 1
_complete = true;
Thread.MemoryBarrier(); // Barrier 2
}
void B()
{
Thread.MemoryBarrier(); // Barrier 3
if (_complete)
{
Thread.MemoryBarrier(); // Barrier 4
Console.WriteLine (_answer);
}
}
}
障碍1和4阻止了这个例子 写“0”。障碍2和3 提供新鲜度保证:他们 确保如果B在A之后运行,则阅读 _complete将评估为true。
因此,如果我们回到您的循环示例......这就是它应该看起来的样子......
private void A()
{
Thread.MemoryBarrier();
while (_someOtherConditions && _foo)
{
//do somthing
Thread.MemoryBarrier();
}
}
答案 4 :(得分:0)
微软自己关于内存障碍的话:
仅在内存排序较弱的多处理器系统上需要MemoryBarrier(例如,采用多个Intel Itanium处理器的系统)。
对于大多数用途,C#lock语句,Visual Basic SyncLock语句或Monitor类提供了更简单的数据同步方法。