使用MemoryBarrier()
System.Threading.Thread.MemoryBarrier()
是这里正确的工具。尽管代码看上去有些笨重,但它比其他可行的替代方案更快。
bool _isDone = false;
public bool IsDone
{
get
{
System.Threading.Thread.MemoryBarrier();
var toReturn = this._isDone;
System.Threading.Thread.MemoryBarrier();
return toReturn;
}
private set
{
System.Threading.Thread.MemoryBarrier();
this._isDone = value;
System.Threading.Thread.MemoryBarrier();
}
}
不要使用volatile
volatile
不能防止读取旧值,因此不符合设计目标。详细信息请参见Jon Skeet的解释或C#中的线程。
请注意,volatile
在许多情况下可能会表现出工作正常的外观,这是由于未定义行为的强内存模型在许多常见系统上存在。但是,依赖于未定义行为可能会导致在其他系统上运行代码时出现错误。一个实际的例子是,如果您正在Raspberry Pi上运行此代码(现在由于.NET Core可行!)。
编辑:在讨论“volatile
在这里无法工作”的说法之后,C#规范确切保证了什么尚不清楚;可以争辩说,volatile
可能被保证工作,尽管延迟更大。 MemoryBarrier()
仍然是更好的解决方案,因为它确保了更快的提交。这种行为在“C# 4 in a Nutshell”的示例中有所解释,讨论在“为什么需要内存屏障?”中。
不要使用锁
锁是一种用于强制进程控制的重型机制。在这样的应用程序中,它们是不必要的笨重工具。
性能影响很小,您可能在轻度使用时不会注意到它,但它仍然是次优的。而且,它可能会对更大的问题如线程饥饿和死锁做出贡献(即使只有轻微的贡献)。
volatile无法工作的详细信息
为了演示此问题,这里有来自Microsoft(通过ReferenceSource)的.NET源代码:
public static class Volatile
{
public static bool Read(ref bool location)
{
var value = location;
Thread.MemoryBarrier();
return value;
}
public static void Write(ref byte location, byte value)
{
Thread.MemoryBarrier();
location = value;
}
}
假设一个线程将 _done = true;
,然后另一个线程读取 _done
来检查它是否为 true
。如果我们将其内联,那么它会是什么样子呢?
void WhatHappensIfWeUseVolatile()
{
Thread.MemoryBarrier();
this._done = true;
var _done = this._done;
Thread.MemoryBarrier();
if (_done == true)
{
}
}
简而言之,
volatile
的问题在于,虽然它确实插入了
MemoryBarrier()
,但在这种情况下,它并没有在我们需要的地方插入。
volatile
无法完全解决此问题的解释。 - Nat