Interlocked.MemoryBarrierProcessWide() 如何在内部工作?

How does Interlocked.MemoryBarrierProcessWide() work internally?

提问人:user22155685 提问时间:10/31/2023 更新时间:10/31/2023 访问量:41

问:

基于这篇文章: https://www.albahari.com/threading/part4.aspx 我们知道,易失性不会阻止先写后读被交换,这可能会产生脑筋急转弯:

class IfYouThinkYouUnderstandVolatile
{
  volatile int x, y;
 
  void Test1()        // Executed on one thread
  {
    x = 1;            // Volatile write (release-fence)
    int a = y;        // Volatile read (acquire-fence)
    ...
  }
 
  void Test2()        // Executed on another thread
  {
    y = 1;            // Volatile write (release-fence)
    int b = x;        // Volatile read (acquire-fence)
    ...
  }
}

A 和 B 都有可能以 0 的值结束(尽管在 X 和 Y 上都使用了 volatile):

基于本文: https://onurgumus.github.io/2021/02/07/Demystifying-the-volatile-keyword.html

我们可以将示例修复为

解决方案一

class IfYouThinkYouUnderstandVolatile
{
  volatile int x, y;
 
  void Test1()        // Executed on one thread
  {
    x = 1;            
    Interlocked.MemoryBarrier();
    int a = y;        
    ...
  }
 
  void Test2()        // Executed on another thread
  {
    y = 1; 
    Interlocked.MemoryBarrier();          
    int b = x;
    ...
  }
}

解决方案二

class IfYouThinkYouUnderstandVolatile
{
  volatile int x, y;
 
  void Test1()        // Executed on one thread
  {
    x = 1;            
    Interlocked.MemoryBarrierProcessWide();
    int a = y;        
    ...
  }
 
  void Test2()        // Executed on another thread
  {
    y = 1; 
    int b = x;
    ...
  }
}

我对解决方案二有疑问。我可以理解可能存在第三种方法。使用相同变量的此类代码可能隐藏在不同的库中,我们可能不知道它。这就是作者使用Interlocked.MemoryBarrierProcessWide();

但是,由于仅在一个地方使用,编译器如何知道它需要在 Test2 方法的指令中插入另一个内存屏障,甚至是访问这些字段的另一个方法?Interlocked.MemoryBarrierProcessWide();

C# .NET 多线程 CLR

评论

1赞 Hans Passant 10/31/2023
它在进程中运行的每个线程中强制设置内存屏障。这种粗糙的大锤需要操作系统支持,它调用操作系统辅助函数。不要使用它。

答:

1赞 Matthew Watson 10/31/2023 #1

该实现仅调用 Windows API 函数 FlushProcessWriteBuffers():

该函数生成处理器间中断 (IPI) 到所有 属于当前进程相关性的处理器。

有关更多详细信息,请参阅此处

因此,要回答您的问题“由于 Interlocked.MemoryBarrierProcessWide();只在一个地方使用,编译器怎么知道它需要在 Test2 方法的指令中插入另一个内存屏障“:

编译器不需要这样做,因为调用 将影响运行任何代码的所有处理器。FlushProcessWriteBuffers()

评论

0赞 user22155685 11/1/2023
对不起,仍然感到困惑,如果一个线程首先执行 Test2() 方法,所以它可能会先执行(编译器可以在 Test2() 中对指令重新排序)然后 b 为 0,然后发生上下文切换,另一个线程正常执行 Test2() 方法,所以也让 y = 0,如何防止这种情况?int b = x;int a = y;FlushProcessWriteBuffers()