4

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

那里的许多示例都显示了一个公共变量的单个“set”或“get”,并详细介绍了内存屏障,但是如果里面有一组更复杂的语句会发生什么?甚至可能执行其他操作的函数调用?

像这样的东西:

lock(sharedObject)
{
  x = 10;
  y = 20;
  z = a + 10;
}

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

lock (sharedObject)
{
  if (y == 10)
  {
     // Do something. 
  }
}

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

4

2 回答 2

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);
}

if(y == 10)
{
// ...
}

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

如果在块内你用andif(y == 10)做某事,你应该返回使用关键字。xylock

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

try
{
  x = 10;
  y = 20;
  Thread.MemoryBarrier();
  z = a + 10;
}
于 2012-07-23T13:51:49.470 回答
1

如果我误解了你的问题,请原谅我(很有可能);但我认为您正在混淆同步可见性的概念。

互斥锁(“互斥”)的全部意义在于确保两个代码块不会同时运行。所以在你的例子中,第一个块:

lock(sharedObject)
{
  x = 10;
  y = 20;
  z = a + 10;
}

...和第二个块:

lock (sharedObject)
{
  if (y == 10)
  {
     // Do something. 
  }
}

...永远不会同时执行。这是lock关键字为您保证的。

因此,每当您代码进入第二个块时,变量xyz应该处于与第一个块的完全执行一致的状态。(这是假设您在任何地方访问这些变量的方式都与您locksharedObject这些片段中的方式相同。)

这意味着从第二个块的角度来看,第一个块内的中间更改的“可见性”是无关紧要的,因为永远不会有任何时候,例如,值的更改x已经发生,但没有发生yor z

于 2012-07-23T04:45:34.700 回答