【问题标题】:Does Interlocked provide visibility in all threads?Interlocked 是否在所有线程中提供可见性?
【发布时间】:2010-10-05 17:40:09
【问题描述】:

假设我有一个变量“counter”,并且有多个线程使用 Interlocked 访问和设置“counter”的值,即:

int value = Interlocked.Increment(ref counter);

int value = Interlocked.Decrement(ref counter);

我可以假设,Interlocked 所做的更改将在所有线程中可见吗?

如果没有,我应该怎么做才能让所有线程同步变量?

编辑:有人建议我使用 volatile。但是当我将“计数器”设置为 volatile 时,会出现编译器警告“对 volatile 字段的引用不会被视为 volatile”。

当我阅读在线帮助时,它说“通常不应使用 ref 或 out 参数传递 volatile 字段”。

【问题讨论】:

  • 是的,互锁递增/递减(在 x86 和 IA-64 上)自动提供所有线程的可见性,因为它具有隐式内存屏障。挥发性不是必需的(尽管它不是非法的)。

标签: c# multithreading visibility interlocked


【解决方案1】:

我可以假设,Interlocked 所做的更改将在所有线程中可见吗?

这取决于您如何读取该值。如果您“只是”阅读它,那么不,除非您将其标记为易失性,否则它不会在其他线程中始终可见。不过,这会导致令人讨厌的警告。

作为替代方案(也是 IMO 的首选),请使用另一个 Interlocked 指令读取它。这将始终在所有线程上看到更新的值:

int readvalue = Interlocked.CompareExchange(ref counter, 0, 0);

返回读取的值,如果为 0,则将其与 0 交换。

动机:警告暗示某事不正确;将这两种技术(易失性和互锁性)结合起来并不是这样做的预期方式。

更新:似乎另一种不使用“易失性”的可靠 32 位读取的方法是使用 Thread.VolatileRead,正如 this answer 中所建议的那样。还有一些证据表明我完全错误地将 Interlocked 用于 32 位读取,例如 this Connect issue,尽管我想知道这种区别本质上是否有点迂腐。

我真正的意思是:不要将此答案用作您的唯一来源;我对此表示怀疑。

【讨论】:

    【解决方案2】:

    x86 CPU 上的 InterlockedIncrement/Decrement(x86 的加锁/减锁)会自动创建 内存屏障,这让所有线程都可以看到(即,所有线程都可以按顺序看到其更新,如顺序内存一致性)。内存屏障使所有待处理的内存加载/存储完成。 volatile 与这个问题无关,尽管 C# 和 Java(以及一些 C/C++ 编译器)强制使用 volatile 来制造内存屏障。但是,联锁操作已经有 CPU 的内存屏障。

    还请查看 stackoverflow 中的 my another answer

    请注意,我假设 C# 的 InterlockedIncrement/Decrement 是到 x86 的锁加/减的内在映射。

    【讨论】:

    • 仅硬件可见性不足以暗示“程序”可见性。
    【解决方案3】:

    实际上,它们不是。如果您想安全地修改counter,那么您做的是正确的事情。但是如果你想直接阅读counter,你需要将它声明为volatile。否则,编译器没有理由相信 counter 会改变,因为 Interlocked 操作在它可能看不到的代码中。

    【讨论】:

    • 这是正确的,尽管volatile 不是唯一可用的方法。 Volatile.Read 可能更合适。
    【解决方案4】:

    Interlocked 确保一次只有 1 个线程可以更新值。为了确保其他线程可以读取正确的值(而不是缓存值),请将其标记为 volatile。

    public volatile int 计数器;

    【讨论】:

    • 当我标记为 volatile 时,有编译器警告。 “对 volatile 字段的引用不会被视为 volatile”。
    • 忽略这种情况下的警告:stackoverflow.com/questions/425132/…
    • 显然,如果您使用 Interlocked,则不需要 Volatile,但如果您在不使用 Interlocked 的情况下进行修改,则需要。
    • 只是为了澄清。如果您要在不获取锁的情况下读取它们,请将它们标记为易失性。使用 Interlocked.Increment 来同步更新,或者在某些东西上使用 lock()。您收到的有关“ref 不被视为 volatile”的警告是通用的,在 Interlocked 的情况下可以忽略。
    • 恐怕这不是正确的答案。任何其他线程都可以看到互锁操作。它对所有线程都有可见性。挥发性不是必需的。如果我错了,请纠正我。
    【解决方案5】:

    没有; Interlocked-at-Write-Only 单独确保代码中的变量读取实际上是新鲜的; 程序也不能正确地从字段中读取 即使在“强内存模型”下,也可能不是线程安全的。这适用于任何形式的分配给线程之间共享的字段。

    这是一个永远不会因 JIT 而终止的代码示例。 (它从 Memory Barriers in .NET 修改为针对问题更新的可运行 LINQPad 程序)。

    // Run this as a LINQPad program in "Release Mode".
    // ~ It will never terminate on .NET 4.5.2 / x64. ~
    // The program will terminate in "Debug Mode" and may terminate
    // in other CLR runtimes and architecture targets.
    class X {
        // Adding {volatile} would 'fix the problem', as it prevents the JIT
        // optimization that results in the non-terminating code.
        public int terminate = 0;
        public int y;
    
        public void Run() {
            var r = new ManualResetEvent(false);
            var t = new Thread(() => {
                int x = 0;
                r.Set();
                // Using Volatile.Read or otherwise establishing
                // an Acquire Barrier would disable the 'bad' optimization.
                while(terminate == 0){x = x * 2;}
                y = x;
            });
    
            t.Start();
            r.WaitOne();
            Interlocked.Increment(ref terminate);
            t.Join();
            Console.WriteLine("Done: " + y);
        }
    }
    
    void Main()
    {
        new X().Run();
    }
    

    来自Memory Barriers in .NET的解释:

    这次是 JIT,而不是硬件。 很明显,JIT 已经缓存了变量 terminate [在 EAX 寄存器中的值,并且] 程序现在卡在上面突出显示的循环中。 .

    在 while 循环中使用 lock 或添加 Thread.MemoryBarrier 都可以解决问题。或者你甚至可以使用Volatile.Read [或volatile 字段]。 这里内存屏障的目的只是为了抑制 JIT 优化。 现在我们已经了解了软件和硬件如何重新排序内存操作,是时候讨论内存屏障了..

    也就是说,在读取端需要一个额外的 barrier 构造,以防止编译和 JIT 重新排序/优化出现问题这是与内存一致性不同的问题!

    在此处添加volatile阻止 JIT 优化,从而“解决问题”,即使这样会导致警告。也可以通过使用Volatile.Read 或导致障碍的各种其他操作之一来纠正此程序:这些障碍与底层硬件内存栅栏一样是 CLR/JIT 程序正确性的一部分。

    【讨论】:

      猜你喜欢
      • 2011-01-29
      • 2016-03-04
      • 1970-01-01
      • 2017-07-01
      • 2016-02-15
      • 2016-05-20
      • 1970-01-01
      • 2013-05-31
      • 2017-11-23
      相关资源
      最近更新 更多