联锁监控.输入并监控.退出模块

本文关键字:监控 退出 模块 输入 | 更新日期: 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内存模型,因为其他人都是这样做的。在这一点上,没有人会创建一个弱实现,因为大多数代码只会崩溃。

当您使用lockMonitor.EnterMonitor.Exit时,这是全栅栏,这意味着它将在内存Thread.MemoryBarrier()中创建一个"屏障",在锁"Monitor.Enter"开始时和锁"Monitor.Exit"结束之前。因此,在之前和之后都不会移动任何操作,但请注意,锁本身内的操作可以从其他线程的角度交换,但这从来都不是问题,因为锁将保证互斥,因此只有一个线程将同时执行锁内的代码。无论如何,重新排序不会发生在单个线程中,也就是说,当多个线程进入同一代码区域时,它们可能会看到指令的顺序不相同。

我强烈建议您在本文中阅读更多关于MemoryBarrier和全围栏和半围栏的信息。

编辑:请注意,这里我描述的事实是lock是完全隔离的,但不是谈论你所知道的"死锁",你所描述的场景永远不会发生,因为就像@Hans提到的那样,重新排序永远不会发生在方法调用中,即:

Method1();
Method2();
Method3();

总是按顺序执行,但其中的指令可能会重新排序,就像多线程执行Method1()内部的代码一样。