比较和交换:通过不同的数据大小进行同步

Compare and Swap : synchronizing via different data sizes

使用 GCC 内置的 C 原子原语,我们可以使用 __atomic_compare_exchange.

执行原子 CAS 操作

与 C++11 的 std::atomic 类型不同,GCC C 原子基元在常规非原子整数类型上运行,包括支持 cmpxchg16b 的平台上的 128 位整数。 (C++ 标准的未来版本可能支持与 std::atomic_view class 模板类似的功能。)

这让我产生疑问:

如果对较大数据大小的原子 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 是否正常失败?这甚至是定义的行为吗?这可能 "do the right thing" 在像 x86_64 这样支持 16b CAS 的典型架构上吗?

编辑:要清楚,因为它似乎造成了混乱,我不是询问是否定义了上述行为按照 C++ 标准。显然,所有 __atomic_* 函数都是 GCC 扩展。 (但是,如果 std::atomic_view 成为标准化,未来的 C++ 标准可能必须定义此类内容。)我更普遍地询问典型现代硬件上原子操作的语义。例如,如果 x86_64 代码有 2 个线程在同一内存地址上执行原子操作,但一个线程使用 CMPXCHG8b,另一个线程使用 CMPXCHG16b,因此一个线程在一个词而另一个对双词执行原子 CAS,这些操作的语义是如何定义的?更具体地说,CMPXCHG16b 是否会失败,因为它观察到由于先前的 CMPXCHG8b?

,数据已从预期值发生突变

换句话说,使用两个不同数据大小(但起始内存地址相同)的两个不同 CAS 操作是否可以安全地用于线程间同步?

一个或另一个先发生,每个操作根据自己的语义进行。

在 x86 CPU 上,这两个操作都需要锁定在整个操作过程中保持的同一缓存行。因此,无论谁首先获得该锁,都不会看到第二个操作的任何影响,无论谁第二个获得该锁,都将看到第一个操作的所有影响。两种操作的语义都将得到充分尊重。

其他硬件可能会以其他方式实现此结果,但如果它没有实现此结果,除非它指定它有限制,否则它就会损坏。

原子的定义不太可能改变,强调我的

In concurrent programming, an operation (or set of operations) is atomic, linearizable, indivisible or uninterruptible if it appears to the rest of the system to occur instantaneously. Atomicity is a guarantee of isolation from concurrent processes.

您的问题是...

What happens if an atomic CAS operation on a larger data size observes a change which happened by an atomic operation on the same memory location, but using a smaller data size?

根据定义,使用原子操作修改的两个重叠内存区域不能同时发生变异,即这两个操作必须线性发生或它们不是原子的。

在检查潜在冲突的原子操作之间的冲突时,硬件通常非常保守。甚至可能会发生针对两个完全不同的、不重叠的地址范围的两个 CAS 操作可能会被检测到相互冲突。

最终,原子数据将位于内存中的某个位置,并且对它的所有访问(或当操作是原子时对相应缓存的访问)将被序列化。由于 CAS 操作应该是原子的,它会作为一个整体执行或根本不执行。

也就是说,其中一个操作会成功,第二个会失败。顺序是不确定的。

来自x86 Instruction Set Reference

This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically. To simplify the interface to the processor’s bus, the destination operand receives a write cycle without regard to the result of the comparison. The destination operand is written back if the comparison fails; otherwise, the source operand is written into the destination. (The processor never produces a locked read without also producing a locked write.)

显然,两个线程都会在锁定读取之后尝试锁定写入(当与 LOCK 前缀一起使用时,即),这意味着它们中只有一个会成功执行 CAS,另一个将读取已更改的值.