执行并发互锁和读取需要内存屏障或锁定

本文关键字:内存 锁定 并发 读取 执行 | 更新日期: 2023-09-27 18:28:57

这是一个简单的问题,但在阅读了《为什么我需要内存屏障?我对此很困惑。

在下面的示例中,假设不同的线程重复调用Increment和Counter:

class Foo{
    int _counter=0;
    public int Counter 
    {
        get { return _counter; }
    }
    public void Increment()
    {
        Interlocked.Increment(ref _counter);
    }
}

对不起,如果我误解了为什么我需要记忆障碍?但这似乎表明,在读取counter的值时,上面的类可能没有提供新鲜度保证。重复访问Counter属性的线程会永远停留在旧的Counter值上吗(因为它被缓存在寄存器中)?

return _counter;之前是否需要内存屏障或锁?

执行并发互锁和读取需要内存屏障或锁定

是return _counter之前的内存屏障或锁;必需的

是的,当然。请考虑以下代码。

while (foo.Counter == 0)
{
  // Do something
}

问题是,如果循环中的内容足够简单,那么C#编译器、JIT编译器或硬件将以这种方式优化代码。

int register = foo._counter;
while (register == 0)
{
  // Do something
}

甚至这个。

if (foo._counter == 0)
{
  START: 
  // Do something
  goto START;
}

请注意,我使用_counter而不是Counter来暗示该属性可能是内联的。然后,更重要的是,JIT编译器可以将_counter的读取"提升"到循环之外,使其只读取一次。

内存屏障本身并不能保证的新鲜度。它们所做的是阻止某些软件或硬件优化对内存的读写进行重新排序。新鲜度保证实际上是一种副作用。

因此,为了结束这些内容,Counter属性应该是这样的。

public int Counter 
{
    get { return Interlocked.CompareExchange(ref _counter, 0, 0); }
}