Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/257.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么写入24位结构不是原子结构(当写入32位结构似乎是原子结构时)?_C#_Struct_Alignment_Atomic_Value Type - Fatal编程技术网

C# 为什么写入24位结构不是原子结构(当写入32位结构似乎是原子结构时)?

C# 为什么写入24位结构不是原子结构(当写入32位结构似乎是原子结构时)?,c#,struct,alignment,atomic,value-type,C#,Struct,Alignment,Atomic,Value Type,毫无疑问,我是个修补匠。出于这个原因(除此之外几乎没有其他原因),我最近做了一个小实验,以证实我的怀疑,即向结构写入不是一个原子操作,这意味着试图强制执行某些约束的所谓“不可变”值类型可能在其目标上失败 我使用以下类型作为说明: struct SolidStruct { public SolidStruct(int value) { X = Y = Z = value; } public readonly int X; public re

毫无疑问,我是个修补匠。出于这个原因(除此之外几乎没有其他原因),我最近做了一个小实验,以证实我的怀疑,即向
结构
写入不是一个原子操作,这意味着试图强制执行某些约束的所谓“不可变”值类型可能在其目标上失败

我使用以下类型作为说明:

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
,事实上,如果一个值是“中间赋值”,同时它被一个单独的线程复制到另一个位置,就会发生这种情况

好吧,没什么大不了的。一种好奇心和更多。但后来我有了这样的预感:我的64位CPU实际上应该能够以原子方式复制64位,对吗?那么,如果我摆脱了
Z
而只坚持
X
Y
,会怎么样呢?只有64位;应该可以在一个步骤中覆盖这些内容

果然奏效了。(我意识到你们中的一些人现在可能眉头紧锁,在想,是的,duh。这有什么意思?幽默我。)当然,我不知道这是否是我的系统所保证的。我对寄存器、缓存未命中等几乎一无所知(我实际上只是在重复我听到的术语,而不理解它们的含义);因此,目前这对我来说都是一个黑匣子

我再次尝试的下一件事是一个由32位组成的结构,使用2
short
字段。这似乎也表现出“原子可分配性”。但后来我尝试了一个24位的结构,使用了3个
字节
字段:禁止

突然间,结构似乎再次受到“中间分配”副本的影响

减少到16位,包含2个字节:再次使用原子


有人能解释一下为什么会这样吗?我听说过“位打包”、“缓存线跨接”、“对齐”等——但我也不知道这一切意味着什么,也不知道这是否与此相关。但我觉得我看到了一种模式,却无法确切说出它是什么;非常感谢清楚。x86 CPU操作以8、16、32或64位进行;操作其他大小需要多次操作。

您要查找的模式是CPU的本机字大小

从历史上看,x86系列本机使用16位值(在此之前是8位值)。因此,您的CPU可以原子化地处理这些值:设置这些值只需一条指令

随着时间的推移,本机元素大小增加到32位,然后增加到64位。在每种情况下,都会添加一条指令来处理这一特定数量的位。但是,为了向后兼容,旧指令仍然保留,因此您的64位处理器可以使用以前所有的本机大小

由于struct元素存储在连续内存中(没有填充,即空空间),因此运行时可以利用这一知识仅对这些大小的元素执行单个指令。简单地说,这产生了您所看到的效果,因为CPU一次只能执行一条指令(尽管我不确定在多核系统上是否能保证真正的原子性)


但是,本机元素的大小从来不是24位。因此,没有一条指令可以写入24位,因此需要多条指令,这样就失去了原子性。

编译器和x86 CPU将小心地只移动结构定义的字节数。没有可以在一次操作中移动24位的x86指令,但是对于8、16、32和64位数据,有单指令移动

如果将另一个字节字段添加到24位结构(使其成为32位结构),您应该会看到原子性返回

某些编译器允许您在结构上定义填充,使其行为类似于本机寄存器大小的数据。如果填充24位结构,编译器将添加另一个字节,将大小“四舍五入”为32位,这样整个结构可以在一条原子指令中移动。缺点是你的结构总是会占用30%以上的内存空间

请注意,内存中结构的对齐对原子性也至关重要。如果一个多字节结构不是从一个对齐的地址开始的,它可能会跨越CPU缓存中的多个缓存线。读取或写入此数据将需要多个时钟周期和多次读取/写入,即使操作码是单个移动指令。因此,如果数据未对齐,即使是单个指令移动也可能不是原子的。x86确实保证了对齐边界上本机大小的读/写操作的原子性,即使在多核系统中也是如此

使用x86锁前缀可以通过多步移动实现内存原子性。但是,应避免这种情况,因为在多核系统中这种情况非常昂贵(锁不仅会阻止其他内核访问内存,还会在操作期间锁定系统总线,这可能会影响磁盘I/O和视频操作。锁还可能迫使其他内核清除其本地缓存)

C#标准(,)关于原子性有这样的说法: 12.5变量引用的原子性 以下数据类型的读写应为原子型:bool、char、byte、sbyte、short、ushort、, uint、int、float和引用类型。此外,还可以读取和写入具有基础类型的枚举类型 在前面的列表中也应是原子的其他类型的读写,包括long、ulong、double、, 和decimal,以及用户定义的类型,不需要是原子的。(emphasis mine)除了设计的库函数之外 为此,不保证原子读-修改-写,例如在增量或 减量,哟