使用GCC内置C原子基元,我们可以使用执行原子CAS操作__atomic_compare_exchange.
与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)));
Run Code Online (Sandbox Code Playgroud)
假设我们有一个类型的共享变量uint128_type,例如:
uint128_type Foo;
Run Code Online (Sandbox Code Playgroud)
现在,假设线程A执行:
Foo expected = { 0, 0 };
Foo desired = { 100, 100 };
int result = __atomic_compare_exchange(
&Foo,
&expected,
&desired,
0,
__ATOMIC_SEQ_CST
);
Run Code Online (Sandbox Code Playgroud)
而线程B做:
uint64_t expected = 0;
uint64_t desired = 500;
int result = __atomic_compare_exchange(
&Foo.x,
&expected,
&desired,
0,
__ATOMIC_SEQ_CST
);
Run Code Online (Sandbox Code Playgroud)
如果线程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操作可以安全地用于线程之间的同步吗?
一个或另一个首先发生,每个操作根据自己的语义进行。
在 x86 CPU 上,这两个操作都需要锁定在整个操作期间保持的同一缓存行。因此,无论谁先获得该锁,都不会看到第二个操作的任何影响,而第二个获得该锁的人将看到第一个操作的所有影响。这两种操作的语义都将得到充分尊重。
其他硬件可能会以其他方式实现此结果,但如果它没有实现此结果,则它已损坏,除非它指定它有限制。