首页 > 解决方案 > Array.Copy 是否维护基于每个元素的原子读取和写入的保证?

问题描述

C# 确保某些类型始终具有原子读取和写入Array.Copy在调用这些类型的两个数组时,我是否有同样的保证?每个元素都是原子读写的吗?我浏览了一些源代码,但没有得出一个可靠的答案。

例如,如果我使用自己的代码来复制两个数组......

static void Copy<T>(T[] source, T[] destination, int length)
{
    for (int i = 0; i < length; ++i)
        destination[i] = source[i];
}

...并称为Copy<int>变体,这保证了每个元素都是原子读取source和原子写入的,destination因为 C# 承诺int读取和写入是原子的。我只是询问是否Array.Copy保持该保证(而不是使用它自己的专用内存块复制例程可能会破坏此保证)。

标签: c#.netatomic

解决方案


Array.Copy() 尝试通过使用memmove()CRT 函数来提高复制效率,这是一种原始的内存到内存的复制,而不考虑存储在数组中的实际类型。如果数组元素类型小于自然处理器字长,则效率会大大提高。

所以你需要知道 memmove() 是否可以提供原子性保证。这是一个 CLR 程序员明确回答的棘手问题。原子性是对象引用的基本特征,当垃圾收集器不能以原子方式更新这些引用时,它就无法正确运行。因此,程序员在 CLR 代码中对此进行了特殊处理,他提供的注释告诉您您想知道什么(编辑以适应):

// The CRT version of memmove does not always guarantee that updates of 
// aligned fields stay atomic (e.g. it is using "rep movsb" in some cases).
// Type safety guarantees and background GC scanning requires object 
// references in GC heap to be updated atomically.

这是一种非常悲观的人生观。但显然不是,当 CLR 作者没有做出这个假设时,你不能假设 Array.Copy() 是原子的。


实际考虑可能需要占上风。在相当常见的架构上,x86 和 x64 有一个 memmove() 实现,它不会使 CLR 内存模型保证更糟,它们一次复制 4 或 8 个对齐的字节。实际上,通用代码替代品中的 for 循环不能保证是原子的,因为 T 不能保证是原子的。

最实际的是你不应该问这个问题。只有当您有另一个线程在没有任何同步的情况下访问数组时,原子性才重要。写入源数组或从目标数组读取。然而,这是有保证的线程竞赛。对源数组的写入最糟糕,副本具有新旧数据的任意混合。从目标数组中读取会随机产生陈旧数据,就像线程错误通常那样。您必须非常勇敢地冒险使用这种代码。


推荐阅读