围绕多个语句的 lock 语句是否确保所有更改对其他线程可见(假设它们输入相同的互斥锁)?

Dod*_*bit 4 c# synchronization locking thread-safety

如果您在一个锁代码块内有多个共享变量的分配,是否一定意味着所有这些更改对其他线程立即可见,一旦它们在同一对象上输入锁语句就可能在其他处理器上运行 - 或者没有这样的保证?

那里的许多示例显示了一个公共变量的单个“设置”或“获取”,并详细介绍了内存屏障,但是如果内部有一组更复杂的语句会发生什么?甚至有可能做其他事情的函数调用?

像这样的东西:

lock(sharedObject)
{
  x = 10;
  y = 20;
  z = a + 10;
}
Run Code Online (Sandbox Code Playgroud)

如果此代码在另一个线程上运行,而该线程可能在另一个处理器上执行,它是否对更改的“可见性”做出任何保证?

lock (sharedObject)
{
  if (y == 10)
  {
     // Do something. 
  }
}
Run Code Online (Sandbox Code Playgroud)

如果答案是否定的- 也许并解释何时这些变化可能变得可见?

Pet*_*hie 5

一个锁块在开始和结束处(块的开始和结束)包括一个内存栅栏。这确保了对内存的任何更改对其他内核(例如,在其他内核上运行的其他线程)都是可见的。在您的示例中,任何其他线程都可以看到第一个锁块中对 x、y、z 的更改。“可见”意味着缓存到寄存器中的任何值都将刷新到内存中,而缓存在 CPU 缓存中的任何内存都将刷新到物理内存中。ECMA 334 详细说明锁块是由 Monitor.Enter 和 Monitor.Exit 包围的块。此外,ECMA 335 详细说明 Monitor.Enter“应隐式执行易失性读取操作...”和 Monitor.Exit“隐式执行易失性写操作。这确实意味着修改不会”

这实际上意味着由 lock 语句保护的任何变量不需要声明为 volatile 以便其他线程可以看到它们的修改。

由于示例代码仅包含一个依赖于单个共享原子操作(读取和写入 y 的单个值)的操作,您可以获得相同的结果:

try
{
  x = 10;
  y = 20;
  Thread.VolatileWrite(ref z, a + 10);
}
Run Code Online (Sandbox Code Playgroud)

if(y == 10)
{
// ...
}
Run Code Online (Sandbox Code Playgroud)

第一个块保证对 x 的写入在写入 y 之前可见,对 y 的写入在写入 z 之前可见。它还保证,如果对 x 或 y 的写入缓存在 CPU 缓存中,则该缓存将在调用 VolatileWrite 后立即刷新到物理内存(从而对任何其他线程可见)。

如果在if(y == 10)块中使用xand做某事y,则应该返回使用lock关键字。

此外,以下内容是相同的:

try
{
  x = 10;
  y = 20;
  Thread.MemoryBarrier();
  z = a + 10;
}
Run Code Online (Sandbox Code Playgroud)

  • 这不是看到或不看到变化的问题。只要更改发生在同一个实例上,其他进程就始终会看到更改。这是何时看到变化的问题,而不是是否能看到变化的问题。锁块本身并不能保证其他线程会看到对 x、y 和 z 的原子更改,也不能保证 x、y 或 z 按照操作发生的顺序进行修改 - 这是唯一可以保证的事情该操作被视为原子操作是因为查看它的其他代码尊重同一锁实例上的互斥锁和锁。 (2认同)