我一直在阅读similar question的答案,但我仍然有些困惑... Abel的回答很好,但这是我不确定的部分:
Interlocked
是否保证对所有线程的原子操作可见性,还是为了保证更改的可见性,我仍然必须在值上使用volatile
关键字吗?
这是我的示例:
volatile int value = 100000; // <-- do I need the volitile keyword
// ....
public void AnotherThreadMethod()
{
while(Interlocked.Decrement(ref value)>0)
{
// do something
}
}
public void AThreadMethod()
{
while(value > 0)
{
// do something
}
}
更新:
我的运动很糟糕,因此我更改了原始示例,因此再次出现:
public class CountDownLatch
{
private volatile int m_remain; // <--- do I need the volatile keyword here?
private EventWaitHandle m_event;
public CountDownLatch(int count)
{
Reset(count);
}
public void Reset(int count)
{
if (count < 0)
throw new ArgumentOutOfRangeException();
m_remain = count;
m_event = new ManualResetEvent(false);
if (m_remain == 0)
{
m_event.Set();
}
}
public void Signal()
{
// The last thread to signal also sets the event.
if (Interlocked.Decrement(ref m_remain) == 0)
m_event.Set();
}
public void Wait()
{
m_event.WaitOne();
}
}
最佳答案
它们*不需要**波动,因为您从未检查互锁变量的值。相反,您始终通过互锁操作来检查返回的值。混合互锁操作和常规分配/比较始终会导致错误代码。
我不确定Reset()函数的意图是什么,但是那段代码在线程间原语中没有位置:您分配给m_remain,直接检查m_remain的值是非常糟糕的。我强烈建议您将其删除:不仅实现不正确,而且我高度怀疑是否需要“重置”计数器生命周期的语义。保持简单:ctor(将代码从Reset移入其中)Signal和Wait是仅需要的三个运算符,它们现在是正确的。
更新了编辑代码后。
忽略了不应该将两者混合的事实,如果最终混合了它们,那么是的,仍然需要使用volatile。 volatile 主要与生成的IL代码和JIT代码有关,以确保始终从实际的内存位置读取该值,并且不会发生优化,例如代码重新排序。不相关的代码段使用互锁操作更新值的事实对读取该值的其他部分无效。没有volatile
属性,编译器/JIT可能仍会生成代码,这些代码将忽略发生在其他位置的写入,如果这些写入是互锁的或直接分配的,则无关紧要。
顺便说一句,有一些有效的模式将普通的读取操作和互锁操作混合在一起,但是它们通常涉及Interlocked.CompareExchange和类似这样的方式:读取当前状态,基于当前状态进行一些计算,尝试将状态替换为互锁的比较交换:如果成功,则返回步骤1。