装配中的数据竞争危险吗?

问题描述 投票:0回答:1

我知道 C 语言中的数据竞争是未定义的行为。 但数据竞争是硬件级别的问题吗?

如果我在汇编中编写一个程序,其中一个线程写入某个地址,而另一个线程使用简单的

mov
指令连续读取它,那么除了读取垃圾之外,读取还会导致任何问题吗? 如果读写大小不同,但是重叠,有问题吗? x86 和 ARM 有什么不同吗?

我不担心读取垃圾的原因是相关部分将受到 seqlock 的保护,并且冲突读取的结果将被丢弃。根据 dl.acm.org/doi/abs/10.1145/2247684.2247688,由于数据竞争是 UB,原子在 C++ 中仍然是必要的,但我认为这可以在汇编中避免。

assembly concurrency x86 arm undefined-behavior
1个回答
0
投票

正确的是,在现代架构的汇编语言中,不同步加载和存储唯一可能的“坏”结果是您可能会读取“不正确”的值(即加载可能返回从未存储的值)。

考虑这个问题的一种方法是,加载指令的架构效果仅涉及写入目标寄存器,或在非法地址(保护违规、页面错误等)的情况下发生故障。故障仅与地址和页表内容等有关,因此不受并发访问的影响。因此,如果您不关心目标寄存器的最终内容(如 seqlock 的情况),则不存在其他风险。

事实上,一般规则是,正确对齐的机器字大小或更小的对象的普通加载和存储自动是原子的,本质上具有 C++

memory_order_relaxed
或更好的语义。在 x86 上,它们甚至具有获取/释放语义。因此,您可以保证读取实际存储的值,并且同一对象的加载和存储将以与程序顺序一致的方式相互观察。例如,如果线程 1 执行
mov [x], 3 / mov [x], 4
而线程 2 执行
mov eax, [x] / mov ebx, [x]
,则不可能最终得到
eax == 4
ebx == 3
。 (请注意,像 ARM 这样的弱有序架构对于访问不同对象没有做出这样的承诺。)

(您可能知道这一点,但只是为了确保未来的读者不会被误导:在 x86 上,像

add [x], 1
这样的非锁定读-修改-写指令执行原子加载和原子存储,类似于
x.store(x.load(memory_order_relaxed)+1, memory_order_relaxed)
,但是不是原子地彼此;来自另一个线程的存储可能发生在两者之间。要获得原子读取-修改-写入,类似于
x.fetch_add(1, memory_order_relaxed)
,您需要
lock add [x], 1
。一个例外是
xchg mem, reg
,它始终具有隐含的
lock
。)

认为,至少在 ARM 和 x86 上,对齐但大小不同的重叠访问也可以保证彼此之间的原子行为,“如预期的那样”。

对于未对齐的访问,事情就更困难了,你必须参考架构规范的细则。 AFAIK 基础 ARMv8-A 除了以原子方式加载/存储未对齐对象的每个字节之外,不保证任何内容。只要不跨越 16 字节边界,具有

FEAT_LSE2
的 ARMv8-4 就能保证原子性。只要不跨越缓存线边界,自 P6 起的 x86 CPU 就能保证原子性。尽管如此,在所有情况下,最坏的情况是您加载一个“撕裂”的值,该值与曾经存储的任何内容都不匹配。

© www.soinside.com 2019 - 2024. All rights reserved.