为什么我们不能锁定值类型?

Oti*_*iel 51 c# multithreading locking reference

我想lock一个Boolean变量时,我遇到了以下错误:

'bool'不是lock语句所要求的引用类型

似乎在lock语句中只允许引用类型,但我不确定我理解为什么.

安德烈亚斯在评论中说:

当[值类型]对象从一个线程传递到另一个线程时,会生成一个副本,因此线程最终会处理2个不同的对象,这是安全的.

这是真的吗?这是否意味着当我执行以下操作时,我实际上在修改和方法x中的两个不同?xToTruexToFalse

public static class Program {

    public static Boolean x = false;

    [STAThread]
    static void Main(string[] args) {

        var t = new Thread(() => xToTrue());
        t.Start();
        // ...
        xToFalse();
    }

    private static void xToTrue() {
        Program.x = true;
    }

    private static void xToFalse() {
        Program.x = false;
    }
}
Run Code Online (Sandbox Code Playgroud)

(仅此代码在其状态下显然无用,仅用于示例)


PS:我知道关于如何正确锁定值类型的这个问题.我的问题与如何但与原因无关.

And*_*ber 43

这里只是猜测......

但是如果编译器允许你锁定一个值类型,你最终都不会锁定任何内容......因为每次你将值类型传递给它时lock,你都会传递它的盒装副本; 一个不同的盒装副本.所以锁就好像是完全不同的对象.(因为,他们实际上是)

请记住,当您为类型参数传递值类型时object,它会被装箱(包装)为引用类型.这使它每次发生时都是一个全新的对象.

  • 关键点在于,值类型每次都会被装入一个不同的对象(拳击与复制并不完全相同,我认为OP和未来的读者值得注意).见[我的回答](http://stackoverflow.com/q/8267344/593627) (4认同)

ole*_*sii 26

您无法锁定值类型,因为它没有sync root记录.

锁定由CLR和OS内部机制执行,这些机制依赖于具有记录的对象,该记录一次只能由单个线程访问 - 同步块根.任何引用类型都有:

  • 指向某种类型的指针
  • 同步块根
  • 指向堆中实例数据的指针


Geo*_*ett 18

它扩展到:

System.Threading.Monitor.Enter(x);
try {
   ...
}
finally {
   System.Threading.Monitor.Exit(x);
}
Run Code Online (Sandbox Code Playgroud)

虽然他们会编译,Monitor.Enter/ Exit需要引用类型,因为值类型每次都会被装箱到不同的对象实例,因此每次调用Enter并将Exit在不同的对象上操作.

从MSDN Enter方法页面:

使用Monitor锁定对象(即引用类型),而不是值类型.将值类型变量传递给Enter时,它将被装箱为对象.如果再次将同一个变量传递给Enter,则会将其作为单独的对象加框,并且该线程不会阻塞.在这种情况下,Monitor应该保护的代码不受保护.此外,当您将变量传递给Exit时,仍会创建另一个单独的对象.因为传递给Exit的对象与传递给Enter的对象不同,所以Monitor会抛出SynchronizationLockException.有关更多信息,请参阅概念主题监视器.


Arm*_*ian 6

我想知道为什么.Net团队决定限制开发人员并允许Monitor只对引用进行操作.首先,您认为锁定System.Int32而不是仅仅为了锁定目的定义专用对象变量是好的,这些储物柜通常不会做任何其他事情.

但是,似乎该语言提供的任何功能必须具有强大的语义,而不仅仅对开发人员有用.所以带有值类型的语义是,只要值类型出现在代码中,它的表达式就会被计算为一个值.因此,从语义的角度来看,如果我们编写`lock(x)'并且x是原始值类型,那么它就像我们所说的那样"锁定一个关键代码块,使变量x的值变为",这听起来更多比奇怪,肯定:).同时,当我们在代码中遇到ref变量时,我们习惯于认为"哦,它是对对象的引用",并暗示引用可以在代码块,方法,类甚至线程和进程之间共享,因此可以用作守护.

换句话说,值类型变量只出现在代码中,以便在每个表达式中评估它们的实际值 - 仅此而已.

我猜这是主要观点之一.

  • +1简单解释,你也回答了@martin brown (2认同)

Aak*_*shM 5

如果从概念上问为什么不允许这样做,我会说答案源于一个事实,即值类型的标识完全等同于其(这就是使其成为值类型的原因)。

所以在宇宙谈论任何地方的任何int 4在谈论同样的事情 -怎能您可能要求独占访问锁就可以了?