Jas*_* Yu 12 c multithreading atomic language-lawyer stdatomic
我可以直接对原子变量进行算术运算吗?
因为我发现 C 标准库提供了很多实用函数,例如atomic_fetch_add执行原子变量和非原子变量之间的加法。但是,我很好奇,由于变量是原子的,我可以直接对其进行算术运算吗?就像下面所示的代码一样:
#include <threads.h>
#include <stdio.h>
#include <stdatomic.h>
atomic_int i = 0;
int run(void* v) {
i += 100; // <- is this operaiton thread-safe?
// atomic_fetch_add(&i, 100);
printf("%d\n", i);
return thrd_success;
}
int main(void) {
thrd_t thread;
thrd_create(&thread, run, NULL);
thrd_join(thread, NULL);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
zwo*_*wol 11
C2011 中明确允许对具有原子类型的变量进行复合赋值:引用N1570 \xc2\xa76.15.6.2p3
\n\n\n形式的复合赋值
\nE1 op= E2与简单赋值表达式等效E1 = E1 op (E2),只是左值E1仅计算一次,并且对于不确定顺序的函数调用,复合赋值的操作是单次计算。如果E1具有原子类型,则复合赋值是具有memory_order_seq_cst内存顺序语义的读取-修改-写入操作。[脚注113]
强调我的。脚注 113 继续给出了当 E1 是原子类型时E1 op= E2如何转换为原始操作的具体示例。<stdatomic.h>
我确信可以应用于原子类型的其他运算符是 postfix++和--,它们也保证执行原子读取-修改-写入 (\xc2\xa76.5.2.4) 和简单赋值,这保证根据需要执行原子加载或存储 (\xc2\xa76.2.6.1)。
注意: 前缀 ++和--不保证执行原子读-修改-写(比较 6.5.2.4 和 6.5.3.1)。
我读了6.2.6.1这句话
\n\n\n具有原子类型的对象的加载和存储是通过
\nmemory_order_seq_cst语义完成的。
这意味着使用原子左值作为大多数其他运算符的操作数是有效的,并且执行原子加载,之后不会对该值进行特殊处理。不要在这部分引用我的话。
\nC++ 可能有不同的规则。我将把 C++ 标准的解释留给其他人。
\n该i += 10;语句保持了 的原子性i,因为它被用作左值表达式。来自 cppreference(粗体我的):
内置递增和递减运算符以及复合赋值是读取-修改-写入原子操作,具有完全顺序一致的排序(就像使用 memory_order_seq_cst 一样)。如果需要不太严格的同步语义,则可以使用标准库函数。
链接页面上给出的示例使用变量的内置(预)增量操作acnt,但它也可以像您的代码一样使用复合赋值。
但是,如果不严格将其用作左值表达式,则更复杂的算术运算可能会导致i变量失去其原子性。来自同一页面:
原子属性仅对左值表达式有意义。左值到右值的转换(模拟从原子位置到 CPU 寄存器的内存读取)会剥离原子性以及其他限定符。