16位对象数组的memcpy可以在两者之间中断吗

Can memcpy of array of 16-bit objects be interrupted in between

全球数据:

uint16_t global_buffer[128];

线程 1:

uint16_t local_buffer[128];
while(true)
{
    ...
    if(data_ready)
        memcpy(global_buffer, local_buffer, sizeof(uint16_t)*128);
}

线程 2:

void timer_handler()
{
    uint16_t value = global_buffer[10];
    //do something with value
}

我的问题是这样做是否安全?我的意思是,是否保证 value 将获得旧值或新值(如果线程 1 memcpy() 被上下文切换中断)? 在更新 16 位值的一个字节而不是第二个字节后,memcpy 是否有可能被中断。在那种情况下,value 将是垃圾。

如果 memcpy 操作只在偶数字节的块之间被中断,我认为这是安全的。

平台:仅限 x86 和 x86-64(实际上仅限 Intel i7 处理器或更新版本)
OS: Linux
编译器:gcc

这将取决于 memcpy() 的实施 - 没有任何保证。即使您知道该实现使它安全,依赖它仍然是不明智的,因此在所有版本和平台上都可能会重复使用此代码或模式。

您可以使用您知道是原子的单词副本来实现您自己的逐字 16 位副本。如何做到这一点需要一个新问题。

除非您在单核 VM 上 运行 否则中断并不真正相关。在具有多核 CPU 的普通系统上,两个线程可以同时在不同的内核上 运行。这就是为什么我们有 C++ std::atomic<> 和 C _Atomic,它们对像 int.

这样的单个变量很有用

这取决于您的 memcpy 实现。任何不可怕的都不会做任何单字节复制,所有 16 位 loads/stores 实际上将是更大的 loads/stores 的一部分(或者可能是 rep movsb 微码的内部) .很难想象一个明智的编译器(不是 DeathStation 9000)会如何选择内联一个可能导致跨 uint16_t 边界撕裂的副本。

但如果您不手动执行此操作(例如使用 AVX 内在函数),它 几乎不可能一些奇怪的优化可以让编译器执行一个字节 load/store.

对于像普通库将用于小尺寸的 SIMD 实现,它归结为 Per-element atomicity of vector load/store and gather/scatter? - 恼人的是,没有来自主要 x86 供应商(AMD 或英特尔)的正式保证。不过,几乎可以肯定它是安全的,尤其是如果整个向量本身是对齐的(因此没有缓存行拆分或页面拆分)。使用 alignas(64) uint16_t global_buffer[128]; 将是确保这一点的好方法。

如果您的总副本大小不是矢量宽度的倍数,重叠副本仍然不会在 一个 uint16_t 内引入撕裂。与前 8 个 uint16_t 和后 8 个 uint16_t 一样,副本大小从 8(完全重叠)到 16(无重叠)数组元素。

顺便说一句,这基本上就是 glibc memcpy 为小副本所做的。一个 4 到 7 字节的 memcpy 是用两个 4 字节加载和 4 字节存储完成的,32 .. 63 字节是用 2x 32 字节向量完成的。 (2 个完全重叠避免了稍后读取时的存储转发停顿,而不是两个非重叠的一半。上端实际上可能让它达到 64 个字节,带有一对全尺寸 AVX 向量。)