为什么不允许锁定(<integer var =“”>),但允许Monitor.Enter(<integer var =“”>)?</integer> </integer>

时间:2009-08-25 17:59:31

标签: c# multithreading

对于以下代码,我得到编译时错误*

  

'int'不是引用类型   锁定声明所需的

int i = 0;
lock(i);

但没有错误:

int i = 0;
Monitor.Enter(i);

据我所知,由于拳击造成的并发症,不应使用值类型进行锁定。但是,为什么它适用于Monitor。

5 个答案:

答案 0 :(得分:15)

原因是lock是一种语言构造,编译器选择在表达式上强加额外的语义。 Monitor.Enter只是一个方法调用,C#编译器不会以任何方式调用特殊情况,因此它会通过正常的重载解析和装箱。

答案 1 :(得分:12)

您绝对不应在Monitor.Enter上使用int。它起作用的原因是因为int被装箱,所以除非你存储对盒装值的引用,否则你将锁定一个临时对象,这意味着你不能在没有异常的情况下调用Monitor.Exit

建议的锁定方法是创建private readonly object并锁定它。对于静态方法,您可以使用private static object

答案 2 :(得分:6)

编译器规范定义the behaviour of lock like so

  

lock语句表达式的编译时类型应为reference-type或&gt;类型参数(第25.1.1节)已知为引用类型。这是一个编译时错误   编译表达式的时间类型以表示值类型。

然后,只要它编译

,它就定义它等同于

由于Monitor.Exit只是一个没有任何约束的方法调用,因此它不会阻止编译器自动装入int并继续其快乐(并且非常)错误的方式。

lock不仅仅是语法糖,foreach不仅仅是语法糖。由此产生的IL转换 not 呈现给代码的其余部分,就好像那是已写的一样。

foreach中修改迭代变量是违法的(尽管在结果输出代码中IL级别没有任何内容可以阻止这种情况)。在锁定中,编译器会阻止编译时已知的值类型,尽管导致IL不关心这一点。

暂且不说:
从理论上讲,编译器可以通过对这个(和其他)方法的深入了解来“祝福”,以便它发现这种情况发生的明显情况,但从根本上说,不可能总是在编译时发现它(考虑到从另一个方法,汇编或通过反射传入的对象)因此烦恼发现任何此类情况可能会适得其反。

编译器对API内部的了解越多,如果您希望将来更改API,您将遇到的问题就越多。

例如,可以添加Monitor.Enter()的重载,该重载接受int并锁定在与int值关联的进程范围的互斥锁上。
这将符合监视器的规格(即使它可能是可怕的),但会导致旧编译器的大量问题,仍然快乐地阻止已经合法的操作。

答案 3 :(得分:3)

出于好奇,你对变量'i'做了什么需要它被锁定?如果所有操作都是增量或其他内容,则使用Interlocked类可能更有效:

Interlocked.Increment(i); // i++ in a thread safe manner

Interlocked类是.NET提供的最轻量级线程同步工具,对于简单的增量,减量,读取或交换,它是最佳选择。

如果您尝试同步一个行为块,那么我只需创建一个可用作同步根的对象:

object syncRoot = new object();

// ...

lock(syncRoot)
{
    // put synced behavior here
}

答案 4 :(得分:0)

我想这是因为Monitor.Enter()是方法调用,所以编译器会自动执行装箱,而lock()是一个语法元素,所以编译器可以检查并在值类型上抛出错误