【发布时间】:2011-06-23 19:59:12
【问题描述】:
我是个修补匠——这一点毫无疑问。出于这个原因(除此之外几乎没有),我最近做了一个小实验来证实我的怀疑,即写入 struct 不是原子操作,这意味着所谓的“不可变" 试图强制执行某些约束的值类型可能会在其目标上失败。
我写了a blog post about this,使用以下类型作为说明:
struct SolidStruct
{
public SolidStruct(int value)
{
X = Y = Z = value;
}
public readonly int X;
public readonly int Y;
public readonly int Z;
}
虽然上述 看起来 像 X != Y 或 Y != Z 永远不可能为真的类型,但实际上如果值为 " mid-assignment”,同时它被一个单独的线程复制到另一个位置。
好的,很重要。好奇心等等。但后来我有了这样的预感:我的 64 位 CPU 应该实际上能够以原子方式复制 64 位,对吗?那么如果我摆脱了Z 并坚持使用X 和Y 怎么办?那只有 64 位;应该可以一步覆盖那些。
果然奏效了。(我意识到你们中的一些人现在可能正在皱起眉头,想,是啊,呃。这有什么有趣的?幽默我。)当然,我不知道我的系统是否能保证这一点。我对寄存器、缓存未命中等几乎一无所知(我实际上只是在重复我听过的术语而没有理解它们的含义);所以目前这对我来说都是一个黑匣子。
我再次尝试的下一个尝试是使用 2 个short 字段的由 32 位组成的结构。这似乎也表现出“原子可分配性”。但是然后我尝试了一个 24 位结构,使用 3 个 byte 字段:不行。
突然间,该结构似乎再次容易受到“中间分配”副本的影响。
低至 16 位,带有 2 个 byte 字段:又是原子的!
有人可以向我解释这是为什么吗?我听说过“位打包”、“缓存行跨界”、“对齐”等——但同样,我真的不知道这意味着什么,也不知道它是否与这里相关。但是我感觉就像我看到了一种模式,但无法准确地说出它是什么;清晰度将不胜感激。
【问题讨论】:
-
CLI 规范保证 32 位目标上的 1、2 和 4 字节内存访问的原子性,64 位目标上的 8 字节内存访问,但前提是变量正确对齐。这不包括结构,您可以使用 StructLayout.Pack 将它们搞砸。
-
对于在搜索多线程问题时发现此问题的任何人,您不应依赖 CPU 原子性来确保线程安全。
标签: c# struct alignment atomic value-type