为什么我们不能locking值types?

我试图lock一个Booleanvariables,当我遇到以下错误:

'bool'不是locking语句所要求的引用types

似乎lock语句中只允许使用引用types,但我不知道为什么。

安德烈亚斯在他的评论中说 :

当[值types]对象从一个线程传递到另一个线程时,会创build一个副本,所以线程最终在两个不同的对象上工作,这是安全的。

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

 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; } } 

(这个代码在状态中显然是无用的,仅仅是这个例子)


PS:我知道如何正确locking值types的这个问题。 我的问题不是关于如何,而是关于为什么

这里只是一个疯狂的猜测…

但是如果编译器允许你locking一个值types,那么你最终将不会locking任何东西……因为每次你将值types传递给lock ,你都会传递一个盒装的副本; 一个不同的盒装副本。 所以锁就好像是完全不同的对象。 (因为,他们实际上是)

请记住,当您为typesobject的parameter passing值types时,它将被装箱(包装)为引用types。 每当发生这种情况时,这就成为一个全新的对象。

您无法locking值types,因为它没有sync rootlogging。

locking由CLR和OS内部机制执行,这些机制依赖于具有只能由单个线程一次访问的logging的对象 – 同步块根。 任何引用types将有:

  • 指向一个types的指针
  • 同步块根
  • 指向堆中实例数据的指针

它扩展到:

 System.Threading.Monitor.Enter(x); try { ... } finally { System.Threading.Monitor.Exit(x); } 

尽pipe它们可以编译,但是Monitor.Enter / Exit需要一个引用types,因为每次对一个不同的对象实例使用一个值types时,每个调用EnterExit操作都会在不同的对象上运行。

从MSDN 进入方法页面:

使用Monitor来locking对象(即引用types),而不是值types。 将值typesvariables传递给Enter时,将其作为对象装箱。 如果您再次将相同的variables传递给Enter,则将其作为单独的对象进行装箱,并且线程不会被阻止。 在这种情况下,Monitor所保护的代码不受保护。 此外,当您将该variables传递给Exit时,还会创build另一个单独的对象。 因为传递给Exit的对象与传递给Enter的对象不同,所以Monitor引发SynchronizationLockException。 有关更多信息,请参阅概念主题监视器。

如果你在概念上问这是不是被允许的话,我会说这个答案来源于这样一个事实:一个值types的身份和它的完全等价(这就是它的一个值types)。

所以宇宙中任何地方的人都在谈论int 4正在谈论同样的事情 – 那么你怎么可能声称独占访问来locking呢?

我想知道为什么.Net团队决定限制开发人员,并允许监控运行的参考只。 首先,你认为locking一个System.Int32而不是定义专门的对象variables只是为了locking的目的,这些locking器通常不会做任何事情。

但是,看起来语言提供的任何特性都必须具有强大的语义,而不仅仅是开发人员有用。 因此,值types的语义是每当一个值types出现在代码中,它的expression式被评估为一个值。 所以,从语义的angular度来看,如果我们写'lock(x)'并且x是一个原始值types,那么它就像我们所说的“locking一个关键代码块和variablesx的值”一样。比奇怪,当然:)。 同时,当我们在代码中遇到refvariables的时候,我们习惯认为“哦,它是对象的引用”,暗示引用可以在代码块,方法,类甚至线程和进程之间共享,因此可以作为守卫。

换句话说, 值typesvariables只出现在代码中,以便在每个expression式中对其实际值进行评估 – 仅此而已。

我想这是主要的一点。

因为值types没有locking语句用来locking对象的同步块。 只有引用types带有types信息,同步块等的开销

如果你把你的引用types框起来,那么你现在有一个包含值types的对象,并可以locking该对象(我期望),因为它现在有额外的开销,对象有一个指向同步块用于locking,指向types信息的指针等)。 正如其他人所说的 – 如果你打开一个对象,每当你打开一个对象,你就会得到一个NEW对象,所以你每次都会locking不同的对象 – 这完全违背了locking的目的。

这可能会工作(虽然这是完全没有意义的,我还没有尝试过)

 int x = 7; object boxed = (object)x; //thread1: lock (boxed){ ... } //thread2: lock(boxed){ ... } 

只要每个人都使用盒装,并且对象盒装只设置一次,你可能会得到正确的locking,因为你locking的盒装对象,它只被创build一次。 不要这样做..这只是一个思想练习(甚至可能不工作 – 就像我说的,我没有testing过)。

至于你的第二个问题 – 不,不会为每个线程复制该值。 两个线程都将使用相同的布尔值,但线程不能保证能看到最新的值(当一个线程设置值时,它可能不会立即写回到内存位置,所以读取该值的任何其他线程将得到一个“旧”结果)。

以下内容来自MSDN:

锁(C#)和SyncLock(Visual Basic)语句可用于确保代码块运行完成,而不会被其他线程中断。 这是通过在代码块的持续期间获得给定对象的互斥锁来实现的。

提供给lock关键字的参数必须是基于引用types的对象,用于定义锁的范围。

我会假设这是因为锁机制使用该对象的一个​​实例来创build互斥锁。

根据这个MSDN线程 ,对引用variables的更改对所有的线程都是不可见的,它们最终可能会使用陈旧的值,而AFAIK我认为值types在线程之间传递时会进行复制。

从MSDN完全引用

澄清赋值为primefaces的事实并不意味着写入立即被其他线程观察到。 如果引用不是易失性的,那么在线程更新之后,另一个线程可能会从引用中读取陈旧值。 但是,更新本身保证是primefaces的(你不会看到底层指针的一部分被更新)。

我认为这是为什么是“因为一位微软工程师以这种方式实现它”的答案。

锁的工作方式是通过在内存中创build一个锁结构表,然后使用对象vtable来logging所需锁的表中的位置。 这表明每个对象都有一个锁,而事实上他们没有锁。 只有那些被locking的人才能做到。 由于值types没有引用,因此没有用于存储locking位置的vtable。

为什么微软select这种奇怪的做事方式是任何人的猜测。 他们可以让Monitor成为一个你不得不实例化的类。 我确定我看过一位MS员工的文章,表示反思这种devise模式是一个错误,但我现在似乎无法find它。