为什么锁可以确保底层监视器被释放并且监视器的直接使用不会?

时间:2013-02-22 08:46:20

标签: c# multithreading events synchronization locking

msdn文章Thread Synchronization (C# Programming Guide)指定:

lock (x)
{
    DoSomething();
}

相当于:

System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
    DoSomething();
}
finally
{
    System.Threading.Monitor.Exit(obj);
} 

然后:

  

“使用lock关键字通常比使用Monitor更受欢迎   直接类,... 因为锁保证底层监视器   即使受保护的代码抛出异常

,也会被释放

这句话是否意味着使用监视器的最后一个代码片段不能确保“即使受保护的代码引发异常,也会释放”基础监视器“?
为什么?

好吧,我很困惑彼此相互矛盾的“等价”而不是(一种用法保险,另一种用途,相当于,不等)。

5 个答案:

答案 0 :(得分:5)

粗体文本所指的情况更像是这样:

Monitor.Enter(obj);
DoSomethingThatThrows();
Monitor.Exit(obj);

其中,如果没有try-finally,抛出异常将绕过Monitor.Exit调用。

答案 1 :(得分:3)

如果lock在功能上等同于提供的代码位,则显然确保它将被释放,因为有一个finally子句。 但是,如果你最后没有使用监视器,你可能会遇到麻烦,造成死锁。

至少,我认为这篇文章的解释意味着什么。

答案 2 :(得分:3)

在x64体系结构上(根据J.Duffy,直到VS2008 JIT - 在某些极端情况下仍然会发生,在没有Any CPU切换的情况下编译/o+时)可能是IL指令放在Monitor.EnterTry语句之间。如果堆栈指针处于此指令时发生异常,则永远不会释放lock

lock关键字的代码生成阻止了这种情况的发生。

这可能就是他们建议使用lock关键字的原因。

参考文献:

Monitor.Enter, Thread aborts

答案 3 :(得分:3)

如果您查看4.0编译器为anycpu生成的IL并将其反转到C#,那么我可以得到的最重要的锁定等效实现看起来像:

object x = new object();
bool lockTaken = false;
// lock
try{
    System.Threading.Monitor.Enter(x, ref lockTaken)
    DoSomeThing();
}
finally
{
   if (lockTaken)
   {
        System.Threading.Monitor.Exit(x);
   }
}

完成所有操作是为了防止出现锁定,线程中止以及永远不会释放锁定的情况,从而导致争用/死锁。该警告告诉您基本上在良好和formost故障情况下平衡Enter和Exit呼叫。 lock语句是实现该目标的最简单的抽象。

基于此IL:

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    .try
    {
        IL_0003: ldsfld object p::x
        IL_0008: dup
        IL_0009: stloc.1
        IL_000a: ldloca.s 0
        IL_000c: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
        IL_0011: nop
        IL_0012: nop
        IL_0013: call void p::DoSomething()
        IL_0018: nop
        IL_0019: nop
        IL_001a: leave.s IL_002c
    } // end .try
    finally
    {
        IL_001c: ldloc.0
        IL_001d: ldc.i4.0
        IL_001e: ceq
        IL_0020: stloc.2
        IL_0021: ldloc.2
        IL_0022: brtrue.s IL_002b

        IL_0024: ldloc.1
        IL_0025: call void [mscorlib]System.Threading.Monitor::Exit(object)
        IL_002a: nop

        IL_002b: endfinally
    } // end handler

    IL_002c: nop
    IL_002d: ldsfld object p::x
    IL_0032: call void [mscorlib]System.Threading.Monitor::Enter(object)
    IL_0037: nop
    .try
    {
        IL_0038: nop
        IL_0039: call void p::DoSomething()
        IL_003e: nop
        IL_003f: nop
        IL_0040: leave.s IL_0050
    } // end .try
    finally
    {
        IL_0042: nop
        IL_0043: ldsfld object p::x
        IL_0048: call void [mscorlib]System.Threading.Monitor::Exit(object)
        IL_004d: nop
        IL_004e: nop
        IL_004f: endfinally
    } // end handler

    IL_0050: nop
    IL_0051: ret

答案 4 :(得分:2)

这意味着当您使用监视器时,您可能会忘记使用 try-finally

实际上你会遇到很多人只是在一个块的开头使用 Monitor.Enter ,在块的末尾使用 Monitor.Exit 。 /> 这并不能保证发生 Monitor.Exit ,因为异常会导致代码在块中间停止运行。