使用GCC内置的C原子操作,我们可以使用__atomic_compare_exchange
执行原子CAS操作。
与C++11的std::atomic
类型不同,GCC C原子操作适用于普通的非原子整数类型,包括在支持cmpxchg16b
的平台上的128位整数。 (未来版本的C++标准可能使用std::atomic_view
类模板支持类似的功能。)
这让我产生了疑问:
如果对较大的数据大小进行原子CAS操作观察到了一个由使用较小数据大小的原子操作在相同的内存位置发生的更改会发生什么?
例如,假设我们有:
struct uint128_type {
uint64_t x;
uint64_t y;
} __attribute__ ((aligned (16)));
假设我们有一个类型为uint128_type
的共享变量,例如:
uint128_type Foo;
现在,假设线程 A 执行以下操作:
Foo expected = { 0, 0 };
Foo desired = { 100, 100 };
int result = __atomic_compare_exchange(
&Foo,
&expected,
&desired,
0,
__ATOMIC_SEQ_CST
);
而线程B则会:
uint64_t expected = 0;
uint64_t desired = 500;
int result = __atomic_compare_exchange(
&Foo.x,
&expected,
&desired,
0,
__ATOMIC_SEQ_CST
);
如果线程A的16字节CAS比线程B的8字节CAS先发生(或反之亦然),会发生什么? CAS会像正常情况下一样失败吗?这种行为是否已经定义?在支持16b CAS的典型架构(如x86_64)上,这是否有可能“做正确的事情”?编辑:为了明确起见,由于它似乎引起了混淆,我并不是在问上述行为是否由C++标准定义。显然,所有__atomic_*函数都是GCC扩展。 (然而,如果std::atomic_view成为标准化,未来的C++标准可能必须定义这种事情。)我更一般地询问了关于原子操作在典型现代硬件上的语义。例如,如果x86_64代码让2个线程对同一内存地址执行原子操作,但一个线程使用CMPXCHG8b,另一个使用CMPXCHG16b,以便一个对单词执行原子CAS,而另一个对双字执行原子CAS,这些操作的语义如何定义?更具体地说,CMPXCHG16b会因为观察到数据由于前面的CMPXCHG8b而发生变异而失败吗?
换句话说,两个使用不同数据大小(但相同的起始内存地址)的CAS操作能安全地用于在线程之间进行同步吗?
__atomic_compare_exchange
的语义。 - David Schwartzdlsym
获取函数地址是一个经典的例子。根据 C 标准,将void *
强制转换为函数指针是未定义的。你必须让函数的语义超越语言。UB 参数仅适用于纯 C/C++ 代码或语义未由其他标准或语义规则指定的情况。 - David Schwartz