联锁监控.输入并监控.退出模块
本文关键字:监控 退出 模块 输入 | 更新日期: 2023-09-27 18:04:24
ECMA-335规范规定如下:
*获取锁(System.Threading.Monitor.)进入或进入一个同步方法)应该隐式地执行一个易失读操作,并释放一个锁(System.Threading.Monitor.)。退出或离开一个同步方法)应该隐式地执行一个易失性写操作。(…)
volatile read具有获取语义,这意味着在CIL指令序列中的read指令之后对内存的任何引用之前,保证读取操作发生。易失性写具有释放语义,即保证在CIL指令序列中的写指令之前的任何内存引用之后进行写操作。*
这意味着编译器不能将语句移出Monitor. enter/Monitor。退出块,但不禁止将其他语句移动到块中。也许,甚至是另一个班长。Enter可以移动到块中(因为可以交换易失性写和易失性读)。那么,下面的代码可以:
class SomeClass
{
object _locker1 = new object();
object _locker2 = new object();
public void A()
{
Monitor.Enter(_locker1);
//Do something
Monitor.Exit(_locker1);
Monitor.Enter(_locker2);
//Do something
Monitor.Exit(_locker2);
}
public void B()
{
Monitor.Enter(_locker2);
//Do something
Monitor.Exit(_locker2);
Monitor.Enter(_locker1);
//Do something
Monitor.Exit(_locker1);
}
}
,将其转换为以下内容的等价物:
class SomeClass
{
object _locker1 = new object();
object _locker2 = new object();
public void A()
{
Monitor.Enter(_locker1);
//Do something
Monitor.Enter(_locker2);
Monitor.Exit(_locker1);
//Do something
Monitor.Exit(_locker2);
}
public void B()
{
Monitor.Enter(_locker2);
//Do something
Monitor.Enter(_locker1);
Monitor.Exit(_locker2);
//Do something
Monitor.Exit(_locker1);
}
}
,可能导致死锁?还是我错过了什么?
ECMA-335规范比CLR(和所有其他实现)使用的规范弱得多。
我记得读到微软第一次尝试移植到IA-64,使用较弱的内存模型。他们有太多自己的代码依赖于双重检查锁定习惯用法(这在较弱的内存模型下被打破了),他们只是在那个平台上实现了较强的模型。
Joe Duffy有一篇很棒的文章,为我们这些普通人总结了(实际的)CLR内存模型。还有一个MSDN文章的链接,该文章更详细地解释了CLR与ECMA-335的不同之处。
我不认为这在实践中是一个问题;假设使用CLR内存模型,因为其他人都是这样做的。在这一点上,没有人会创建一个弱实现,因为大多数代码只会崩溃。
当您使用lock
或Monitor.Enter
和Monitor.Exit
时,这是全栅栏,这意味着它将在内存Thread.MemoryBarrier()
中创建一个"屏障",在锁"Monitor.Enter
"开始时和锁"Monitor.Exit
"结束之前。因此,在锁之前和之后都不会移动任何操作,但请注意,锁本身内的操作可以从其他线程的角度交换,但这从来都不是问题,因为锁将保证互斥,因此只有一个线程将同时执行锁内的代码。无论如何,重新排序不会发生在单个线程中,也就是说,当多个线程进入同一代码区域时,它们可能会看到指令的顺序不相同。
我强烈建议您在本文中阅读更多关于MemoryBarrier
和全围栏和半围栏的信息。
编辑:请注意,这里我描述的事实是lock
是完全隔离的,但不是谈论你所知道的"死锁",你所描述的场景永远不会发生,因为就像@Hans提到的那样,重新排序永远不会发生在方法调用中,即:
Method1();
Method2();
Method3();
总是按顺序执行,但其中的指令可能会重新排序,就像多线程执行Method1()
内部的代码一样。