【问题标题】:Does Interlocked guarantee visibility to other threads in C# or do I still have to use volatile?Interlocked 是否保证对 C# 中其他线程的可见性,还是我仍然必须使用 volatile?
【发布时间】:2011-01-29 08:00:40
【问题描述】:

我一直在阅读similar question 的答案,但我仍然有点困惑...... Abel 的答案很好,但这是我不确定的部分:

...声明一个变量 volatile 使得它对每一个变量都是 volatile 使用权。无法强制执行此操作 以任何其他方式行为,因此易变 不能用互锁代替。 这在以下场景中是必需的 其他库、接口或 硬件可以访问您的变量和 随时更新,或者最需要 最新版本。

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();
    }
}

【问题讨论】:

  • 很抱歉进行了多次编辑... Remus 一直是一项很好的运动。感谢您的信息! :)

标签: c# multithreading thread-safety volatile interlocked


【解决方案1】:

它们不需要**波动性,因为您从不检查联锁变量的值。相反,您总是检查联锁操作返回的值返回。混合互锁操作和普通赋值/比较总是会导致错误的代码。

我不确定 Reset() 函数的意图是什么,但那段代码在线程间原语中没有位置:你分配给 m_remain,你直接检查 m_remain 的值,这很糟糕。我强烈建议您将其删除:不仅实施不正确,而且我高度怀疑是否需要“重置”计数器的中年寿命。保持简单:ctor(将代码从 Reset 移到其中)Signal 和 Wait 是仅有的三个需要的运算符,它们现在是正确的。

更新在您编辑代码之后。

忽略您不应该将两者混合的事实,如果您最终将它们混合,那么是的,仍然需要 volatile。 Volatile 主要是关于 IL 代码和生成的 JIT 代码,以确保始终从实际内存位置读取值并且不会发生优化,例如代码重新排序。一段不相关的代码使用互锁操作更新值这一事实对读取该值的其他部分没有影响。如果没有volatile 属性,编译器/JIT 仍可能生成忽略其他地方发生的写入的代码,与写入是互锁的还是直接赋值无关。

顺便说一句,有一些混合普通读取和互锁操作的有效模式,但它们通常涉及 Interlocked.CompareExchange 和这样的 go:读取当前状态,根据当前状态进行一些计算,尝试将状态替换为互锁比较-exchange:如果成功则正常,如果不成功则丢弃计算结果并返回步骤1。

【讨论】:

  • 对不起,我把例子放回去了……我真的不是故意骗你的! :)
  • 所以重置功能消失了......它真的没有任何意义。我认为它可能对重用同一个闩锁很有用,但它比其他任何事情都更加混乱。
  • 我认为重置的目的。如果您确定只在安全的上下文中调用它会很好,但您永远无法知道。提供一个开发人员不能滥用的 API,而不是提供一个可能被滥用的 API 并要求用户小心,要容易得多。没有人看说明书……
  • 模式“普通读取变量;计算调整值;Interlock.CompareExchange 变量如果等于旧值则为新值;如果 CompareExchange 失败则重复所有操作”模式是否有任何不正确之处?如果原始读取过时,则循环将运行额外时间,除非某些外部实体碰巧调整写入与一次读取匹配的值,但在通常情况下,它只需要一个互锁操作而不是两个。
【解决方案2】:

我认为 System.Threading.Thread.VolatileRead(ref myVariable) 可能是您正在寻找的。与 Interlocked.Increment 结合使用,可用于保证更改是原子的,并且您读取的值是最新的。

【讨论】:

    猜你喜欢
    • 2010-10-05
    • 1970-01-01
    • 1970-01-01
    • 2012-01-08
    • 2016-12-28
    • 2017-05-06
    • 2020-11-29
    • 2017-11-04
    • 2018-02-28
    相关资源
    最近更新 更多