C++原子抢占安全吗

Are C++ atomics preemption safe?

本文关键字:安全 C++      更新时间:2023-10-16

根据我对原子的理解,它们是特殊的汇编指令,可以保证SMP系统中的两个处理器不能同时写入同一内存区域。例如,在PowerPC中,原子增量看起来像:

retry:
lwarx  r4, 0, r3 // Read integer from RAM location r3 into r4, placing reservation.
addi   r4, r4, 1 // Add 1 to r4.
stwcx. r4, 0, r3 // Attempt to store incremented value back to RAM.
bne-   retry     // If the store failed (unlikely), retry.

然而,这并不能保护这四条指令不被一个中断和另一个正在调度的任务抢占。为了防止抢占,你需要在输入代码之前进行中断锁定。

从我对C++原子的了解来看,如果需要的话,它们似乎会强制执行锁。所以我的第一个问题是

  1. C++标准是否保证在原子操作期间不会发生抢占?如果是,我在标准中的哪里可以找到这个

我在英特尔电脑上检查了atomic<int>::is_always_lock_free,结果是true。在我对上述汇编块的假设下,这让我感到困惑。在深入研究英特尔汇编(我不熟悉)后,我发现lock xadd DWORD PTR [rdx], eax正在发生。所以我的问题是

  1. 某些体系结构是否提供原子相关指令,以保证不进行预处理?还是我的理解错了

最后我想知道compare_exchange_weakcompare_exchange_strong语义-

  1. 区别在于重试机制还是其他原因

编辑:看完答案后,我对还有一件事感到好奇

  1. 原子成员函数操作fetch_addoperator++等,它们是强还是弱

这类似于这个问题:std::atomic中的任何内容都是免费等待的?

以下是锁定自由和等待自由的一些定义(均取自维基百科):

如果程序线程运行足够长的时间时,至少有一个线程取得了进展,则算法是无锁

如果每个操作都有算法在操作完成前要执行的步骤数的限制,则算法是无等待

带有重试循环的代码是无锁的:线程只需要在存储失败时执行重试,但这意味着在此期间必须更新了值,因此其他线程必须已经取得了进展。

关于锁自由度,线程是否可以在原子操作的中间被抢占并不重要。

某些操作可以转换为单个原子操作,在这种情况下,此操作是等待空闲,因此不能在中途抢占。然而,哪些操作实际上是免费等待的,这取决于编译器和目标体系结构(如我在引用的SO问题中的回答所述)。

关于compare_exchange_weakcompare_exchange_strong之间的差异,弱版本可能会错误地失败,即即使比较实际上是真的,它也可能失败。这可能发生在具有LL/SC的体系结构上。假设我们使用compare_exchange_weak更新一些具有期望值A的变量。LL从变量加载值A,在执行SC之前,变量将更改为B然后返回A。因此,即使变量包含与以前相同的值,对B的中间更改也会导致SC(因此compare_exchange_weak)失败。compare_exchange_strong不能错误地失败,但为了实现这一点,它必须在具有LL/SC的体系结构上使用重试循环。

我不完全确定你所说的CCD_;强或弱";。fetch_add不能失败-它只是通过添加提供的值来执行某个变量的原子更新,并返回变量的旧值。这是否可以转换为单个指令(如在Intel上),或转换为使用LL/SC(Power)或CAS(Sparc)的重试循环,取决于目标体系结构。无论哪种方式,都可以保证变量得到正确更新。

C++标准是否保证在原子操作期间不会发生抢占?如果是,我在标准中的哪里可以找到这个?

不,不是。由于代码真的无法判断这种情况是否发生(根据情况,它与原子操作之前或之后的抢占无法区分),因此没有理由这样做。

某些体系结构是否提供保证不抢占的原子相关指令?还是我的理解错了?

没有意义,因为操作无论如何都必须看起来是原子的,所以在观察到的行为中,期间的抢占总是与之前或之后的抢占相同。如果您编写的代码能够看到原子操作期间的抢占会导致与之前的抢占或之后的抢占不同的可观察效果,那么该平台就会崩溃,因为操作的行为不是原子性的。