如果在一个锁定代码块中有多个共享变量赋值,它是否一定意味着所有这些更改都会立即被其他线程看到,一旦它们在同一个对象上输入一个锁定语句就可能在其他处理器上运行 – 或者没有这样的保证?
很多例子都显示了一个公共变量的“set”或“get”,并详细介绍了内存障碍,但如果内部有更复杂的语句,会发生什么?有可能甚至函数调用做其他事情?
像这样的东西:
lock(sharedObject)
{
x = 10;
y = 20;
z = a + 10;
}
如果此代码在另一个可能在另一个处理器上执行的线程上运行,它是否会对更改的“可见性”做出任何保证?
lock (sharedObject)
{
if (y == 10)
{
// Do something.
}
}
如果答案是否定的 – 也许并解释这些变化何时可见?
解决方法:
锁定块在开始和结束时(块的开始和结束)包括存储器栅栏.这可确保对内存的任何更改对其他内核(例如,在其他内核上运行的其他线程)可见.在您的示例中,对第一个锁定块中的x,y,z的更改将对任何其他线程可见. “可见”表示缓存到寄存器中的任何值都将刷新到内存,并且缓存在CPU缓存中的任何内存都将刷新到物理内存. ECMA 334详细说明锁定块是由Monitor.Enter和Monitor.Exit包围的块.此外,ECMA 335详细说明Monitor.Enter“将隐式执行易失性读操作……”并且Monitor.Exit“隐式执行易失性写操作.这确实意味着修改将不会被其他内核/线程看到,直到锁定块的结尾(在Monitor.Exit之后),但是如果对这些变量的所有访问都被锁保护,则无论如何都不能同时访问不同内核/线程上的所述变量.
这实际上意味着锁定语句保护的任何变量都不需要声明为volatile,以使其修改对其他线程可见.
由于示例代码仅包含依赖于单个共享原子操作的操作(对y读取和写入单个值),因此可以获得相同的结果:
try
{
x = 10;
y = 20;
Thread.VolatileWrite(ref z, a + 10);
}
和
if(y == 10)
{
// ...
}
第一个块保证在写入y之前对x的写入是可见的,并且在写入z之前对y的写入是可见的.它还保证,如果对x或y的写入缓存在CPU缓存中,则在调用VolatileWrite之后,该缓存将立即刷新到物理内存(因此对任何其他线程可见).
如果在if(y == 10)块中使用x和y执行某些操作,则应返回使用lock关键字.
此外,以下内容将完全相同:
try
{
x = 10;
y = 20;
Thread.MemoryBarrier();
z = a + 10;
}