3

msdn 文章线程同步(C# 编程指南)指定:

lock (x)
{
    DoSomething();
}

相当于:

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

然后是:

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

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

好吧,我对相互矛盾的断言“等效”而不是(一种用法保证,另一种,等效,不)大致相同感到困惑。

4

5 回答 5

5

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

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

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

于 2013-02-22T08:50:44.647 回答
3

如果 lock 在功能上等同于所提供的代码,那么两者显然都确保它会被释放,因为有一个 finally 子句。但是,如果你只使用monitor而不使用finally,你可能会遇到麻烦,导致死锁。

至少,我认为这篇文章的解释是这样的。

于 2013-02-22T08:50:38.333 回答
3

在 x64 架构上(直到根据 J.Duffy 的 VS2008 JIT - 在某些极端情况下,在编译时仍然会发生这种情况,Any CPU而无需/o+切换。)有可能在Monitor.EnterandTry语句之间放置了一条 IL 指令。如果堆栈指针位于该指令处时发生异常,则lock永远不会释放。

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

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

参考:

Monitor.Enter,线程中止

于 2013-02-22T09:13:11.427 回答
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);
   }
}

所做的一切都是为了防止出现锁被占用、线程中止并且锁永远不会释放的情况,从而导致竞争/死锁。警告基本上告诉您在良好和最失败的情况下平衡 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
于 2013-02-22T09:48:05.010 回答
2

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

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

于 2013-02-22T08:52:15.173 回答