当我遇到以下错误时,我试图lock
Boolean
变量:
'bool'不是lock语句
所要求的引用类型
lock
语句中似乎只允许引用类型,但我不确定为什么会这样理解。
当[一个值类型]对象从一个线程传递到另一个线程时,会产生一个副本,因此线程最终会处理2个不同的对象,这是安全的。
这是真的吗?这是否意味着当我执行以下操作时,我实际上在x
和xToTrue
方法中修改了两个不同的xToFalse
?
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;
}
}
(仅此代码在其状态下显然无用,仅用于示例)
P.S:我在How to properly lock a value type知道这个问题。我的问题与如何无关,而与为何无关。
答案 0 :(得分:41)
这里只是猜测......
但是如果编译器让你锁定一个值类型,你最终都不会锁定任何内容......因为每次你将值类型传递给lock
时,你都会传递一个盒装副本它;一个不同的盒装副本。所以锁就好像是完全不同的对象。 (因为,他们实际上是)
请记住,当您为类型为object
的参数传递值类型时,它会被装箱(包装)为引用类型。这使它每次发生时都是一个全新的对象。
答案 1 :(得分:26)
您无法锁定值类型,因为它没有sync root
记录。
锁定由CLR和OS内部机制执行,这些机制依赖于具有记录的对象,该记录一次只能由单个线程访问 - 同步块根。任何引用类型都有:
答案 2 :(得分:18)
它扩展为:
System.Threading.Monitor.Enter(x);
try {
...
}
finally {
System.Threading.Monitor.Exit(x);
}
虽然他们会编译,但Monitor.Enter
/ Exit
需要引用类型,因为每次调用Enter
和{{1时,值类型都会被装箱到不同的对象实例将在不同的对象上运行。
来自MSDN Enter method页面:
使用Monitor锁定对象(即引用类型),而不是值类型。将值类型变量传递给Enter时,它将被装箱为对象。如果再次将同一个变量传递给Enter,则将其作为单独的对象加框,并且该线程不会阻塞。在这种情况下,Monitor应该保护的代码不受保护。此外,当您将变量传递给Exit时,仍会创建另一个单独的对象。因为传递给Exit的对象与传递给Enter的对象不同,所以Monitor会抛出SynchronizationLockException。有关更多信息,请参阅概念主题监视器。
答案 3 :(得分:6)
我想知道为什么.Net团队决定限制开发人员并允许Monitor只对引用进行操作。首先,您认为锁定System.Int32
而不是仅仅为了锁定目的而定义专用对象变量会很好,这些储物柜通常不会做任何其他事情。
但是,似乎该语言提供的任何功能必须具有强大的语义,而不仅仅对开发人员有用。所以带有值类型的语义是,只要值类型出现在代码中,它的表达式就会被计算为一个值。因此,从语义的角度来看,如果我们编写`lock(x)'并且x是原始值类型,那么它就像我们所说的那样“锁定一个关键代码块,使变量x的值变为”,这听起来更多比奇怪,肯定:)。同时,当我们在代码中遇到ref变量时,我们习惯于认为“哦,它是对对象的引用”并且暗示引用可以在代码块,方法,类甚至线程和进程之间共享,因此可以用作后卫。
用两个词来说,值类型变量只出现在代码中,以便在每个表达式中评估它们的实际值 - 仅此而已。
我猜这是主要观点之一。
答案 4 :(得分:4)
如果你问概念为什么不允许这样做,我会说答案源于价值类型的身份完全等同于< strong> value (这就是使其成为值类型的原因)。
所以,在宇宙中任何地方谈论int
4
的人都在谈论同样的事情 - 那么你怎么可能声称拥有锁定它的独占权?< / p>
答案 5 :(得分:2)
因为值类型没有lock语句用来锁定对象的同步块。只有引用类型带有类型信息,同步块等的开销。
如果你打包你的引用类型,那么你现在有一个包含值类型的对象,并且可以锁定该对象(我期望),因为它现在具有对象所具有的额外开销(指向同步块的指针,用于锁定,指向类型信息的指针等)。正如其他人所说的那样 - 如果你装箱一个物体,你每次装箱都会得到一个新物品,这样你每次都会锁定不同的物体 - 这完全违背了锁定的目的。
这可能会奏效(尽管它完全毫无意义,我还没试过)
int x = 7;
object boxed = (object)x;
//thread1:
lock (boxed){
...
}
//thread2:
lock(boxed){
...
}
只要每个人都使用盒装,并且盒装的对象只设置一次,你可能会得到正确的锁定,因为你锁定盒装对象并且它只被创建一次。不要这样做......虽然这只是一次思考练习(甚至可能不起作用 - 就像我说的那样,我还没有测试过它。)
关于你的第二个问题 - 不,不会为每个线程复制该值。两个线程都将使用相同的布尔值,但线程不能保证看到它的最新值(当一个线程设置值时,它可能不会立即写回内存位置,因此读取该值的任何其他线程将获得一个“过去的结果”。
答案 6 :(得分:1)
以下内容摘自MSDN:
锁(C#)和SyncLock(Visual Basic)语句可用于确保代码块在不中断其他线程的情况下运行完成。这是通过在代码块的持续时间内为给定对象获得互斥锁来实现的。
和
提供给lock关键字的参数必须是基于引用类型的对象,并用于定义锁的范围。
我认为这部分是因为锁机制使用该对象的实例来创建互斥锁。
答案 7 :(得分:1)
根据这个MSDN Thread,对参考变量的更改可能对所有线程都不可见,并且它们可能最终使用陈旧值,而AFAIK我认为值类型在它们之间传递时会复制线程。
完全引自MSDN
澄清分配是原子的事实也很重要 并不意味着写入会立即被其他人观察到 线程。如果引用不是易失性的,那么它是可能的 一段时间从引用中读取陈旧值的另一个线程 你的线程更新后。但是,更新本身就是 保证是原子的(你不会看到底层的一部分 指针得到更新)。
答案 8 :(得分:0)
我认为这就是为什么“因为微软工程师以这种方式实现它”的答案之一。
锁定工作的方式是在内存中创建一个锁定结构表,然后使用对象vtable来记住表中所需锁定的位置。这给出了每个对象都具有锁定的外观,而实际上它们没有。只有那些被锁定的人才会这样做。由于值类型没有引用,因此没有vtable来存储锁定位置。
为什么微软选择这种奇怪的做事方式是任何人的猜测。他们本可以让Monitor成为你必须实例化的一个类。我确信我看到一位MS员工的一篇文章表示反思这种设计模式是错误的,但我现在似乎无法找到它。