【发布时间】:2011-03-25 14:10:14
【问题描述】:
每个人都说不可变对象是线程安全的,但这是为什么呢?
以在多核 CPU 上运行以下场景为例:
- Core 1 读取内存位置
0x100的对象,并缓存在Core 1 的L1/L2 缓存中; - GC 在该内存位置收集该对象,因为它已符合条件并且
0x100可用于新对象; - Core 2 分配一个(不可变)对象,该对象位于地址
0x100; - Core 1 获取对这个新对象的引用并在内存位置
0x100读取它。
在这种情况下,当核心 1 请求位置 0x100 的值时,它是否有可能从其 L1/L2 缓存中读取过时数据?我的直觉说这里仍然需要一个内存门来确保 Core 1 读取正确的数据。
上述分析是否正确,是否需要记忆门,还是我遗漏了什么?
更新:
我在这里描述的情况是每次 GC 执行收集时发生的更复杂的版本。当 GC 收集时,内存被重新排序。这意味着对象所在的物理位置发生了变化,L1/L2 必须失效。上面的例子大致相同。
由于可以合理地期望 .NET 确保在重新排序内存后,不同的内核看到正确的内存状态,因此上述情况也不会成为问题。
【问题讨论】:
-
对象的不变性和对它的引用的不变性(或缺乏)是有区别的。
-
@Pieter:不知道 .NET 中的 GC 是如何工作的,但是为什么它会释放内存 0x100,因为已经有对对象的引用?
-
我同意其他一些评论者的观点。在这种情况下,实际的对象是不可变的,reference 不是——核心 2 没有改变一个不可变的对象,它创建了一个,并将其放在 0x100。 Core 1 的引用就是这样——一个引用,它实际上并不是不可变对象的一部分——它更像是一个指针。所以是的——我想你会想要一个内存门,我可能会选择在核心 2 交换对象的 Interlocked Exchange 中。
-
也许我误解了一些东西,但这句话对我来说似乎是假的:“据我所知,Core 1 在这种情况下完全有可能从其 L1/ 读取位置 0x100 的内存L2 缓存,因此它可以读取过时的数据。”如果我只是更改了位置 0x100 的内容,那不会使包含该地址的缓存行无效吗?
-
@JMarsch 如你所说;这听起来不仅合理,而且是一项基本要求。
标签: .net multithreading concurrency immutability