代码之家  ›  专栏  ›  技术社区  ›  Chaowlert Chaisrichalermpol

联锁是否在所有线程中提供可见性?

  •  7
  • Chaowlert Chaisrichalermpol  · 技术社区  · 16 年前

    假设我有一个变量“counter”,并且有几个线程通过使用联锁来访问和设置“counter”的值,即:

    int value = Interlocked.Increment(ref counter);
    

    int value = Interlocked.Decrement(ref counter);
    

    我可以假定,由联锁所做的更改将在所有线程中可见吗?

    如果不是,我该怎么做才能使所有线程同步变量?

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

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

    5 回复  |  直到 7 年前
        1
  •  6
  •   Community CDub    8 年前

    x86 CPU(x86的lock add/dec)上的联锁增量/减量正在自动创建 记忆障碍 它使所有线程都可见(即,所有线程都可以按顺序查看其更新,如顺序内存一致性)。内存屏障将完成所有挂起的内存加载/存储。 volatile 与C语言和Java(以及一些C/C++编译器)的执行无关 不稳定的 制造记忆障碍。但是,联锁操作已经受到CPU的内存限制。

    请也看看 my another answer 在StackOverflow中。

    请注意,我假设C的互锁增量/减量是到x86的lock add/dec的内在映射。

        2
  •  6
  •   Community CDub    8 年前

    我可以假定,由联锁所做的更改将在所有线程中可见吗?

    这取决于您如何读取该值。如果您“只是”阅读它,那么不,除非您将其标记为易失性,否则它在其他线程中并不总是可见的。但这会引起一个恼人的警告。

    作为另一种选择(也是更可取的IMO),使用另一个互锁指令读取它。这将始终看到所有线程上的更新值:

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

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

    动机:警告意味着有些事情不对劲;结合这两种技术(易失性和互锁性)并不是达到目的的方法。

    更新:另一种不使用“volatile”的可靠32位读取方法似乎是使用 Thread.VolatileRead 如建议 this answer . 还有一些证据表明我在使用 Interlocked 例如,对于32位读取 this Connect issue 不过,我想知道这种区别在本质上是否有点学究。

    我真正的意思是:不要把这个答案作为你唯一的来源,我对此有疑问。

        3
  •  3
  •   MSN    16 年前

    实际上,它们不是。如果您想安全地修改 counter 那么你做的是正确的。但是如果你想读 柜台 直接申报为 volatile . 否则,编译器没有理由相信 柜台 会改变因为 Interlocked 操作的代码可能看不到。

        4
  •  1
  •   Peter Morris    16 年前

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

    公共易失性int计数器;

        5
  •  1
  •   user2864740 Heinzi    7 年前

    没有; 仅在写入时互锁 确保在代码中读取的变量实际上是新的; 不能正确读取字段的程序。 可能不是线程安全的 即使是在“强记忆模式”下。这适用于分配给线程之间共享的字段的任何形式。

    下面是一个永远不会终止的代码示例 由于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();
    }
    

    解释来自 .NET中的内存屏障 :

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

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

    也就是说,另外 障碍 在读取端需要构造 防止编译和JIT重新排序/优化问题 : 这是一个不同于记忆连贯性的问题!

    添加 不稳定的 这里将 防止 JIT优化,从而“解决问题”,即使这样会导致警告。这个程序也可以通过使用 易挥发的 或者引起屏障的其他各种操作之一:这些屏障与底层硬件内存屏障一样,都是clr/jit程序正确性的一部分。