std::atomic_ref 是如何为非原子类型实现的?

How is std::atomic_ref implemented for non-atomic types?

我想知道如何为非原子对象有效地实现 std::atomic_ref(每个对象一个 std::mutex),因为以下 属性 似乎很难执行:

Atomic operations applied to an object through an atomic_ref are atomic with respect to atomic operations applied through any other atomic_ref referencing the same object.

特别是以下代码:

void set(std::vector<Big> &objs, size_t i, const Big &val) {
    std::atomic_ref RefI{objs[i]};
    RefI.store(val);
}

似乎很难实现,因为 std::atomic_ref 每次都需要以某种方式选择相同的 std::mutex(除非它是所有相同类型的对象共享的大主锁)。

我错过了什么吗?或者每个对象负责实现 std::atomic_ref,因此要么是原子的,要么带有 std::mutex?

实现可以使用基于对象地址的散列来确定在执行操作时获取一组锁中的哪一个。

该实现与 std::atomic<T> 本身完全 几乎相同。这不是一个新问题。

请参阅 std::atomic / std::atomic_ref 静态散列 table 锁的典型实现,按地址索引,用于非无锁对象。哈希冲突只会导致额外的争用,而不是正确性问题。 (死锁仍然是不可能的;锁只被原子函数使用,它们从不尝试一次取 2 个。)

例如,在 GCC 上,std::atomic_ref 只是另一种在对象上调用 __atomic_store 的方法。 (见GCC manual: atomic builtins

编译器知道T是否小到可以无锁。如果没有,它调用将使用锁的 libatomic 库函数。

(有趣的事实:这意味着只有当对象对 atomic<T> 有足够的对齐时它才有效。但是在包括 x86 在内的许多 32 位平台上,uint64_t 可能只有 4 字节对齐。 atomic_ref 在这样的对象上将编译和 运行,但如果编译器在 32 位模式下使用 SSE 8 字节 load/store 来实现它,则实际上不是原子的。幸运的是没有危险对于具有 alignof(T) == sizeof(T) 的对象,就像 64 位架构上的大多数原始类型一样。)