std::atomic::is_always_lock_free = true 的真正含义是什么?

HCS*_*CSF 7 c++ lock-free c++11 stdatomic

我有以下代码:

#include <atomic>

int main () {
    std::atomic<uint32_t> value(0);
    value.fetch_add(1, std::memory_order::relaxed);
    static_assert(std::atomic<uint32_t>::is_always_lock_free);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它可以编译,所以这意味着std::atomic<uint32_t>::is_always_lock_free是真的。

然后,使用 gcc 10 和 的汇编代码如下所示-std=c++20 -O3 -mtune=skylake-avx512 -march=skylake-avx512

0000000000401050 <main>:
  401050:       c7 44 24 fc 00 00 00    mov    DWORD PTR [rsp-0x4],0x0
  401057:       00 
  401058:       f0 ff 44 24 fc          lock inc DWORD PTR [rsp-0x4]
  40105d:       31 c0                   xor    eax,eax
  40105f:       c3                      ret    
Run Code Online (Sandbox Code Playgroud)

许多帖子指出,读-修改-写操作(fetch_add()此处)不能是没有锁的原子操作。

我的问题是std::atomic::is_always_lock_free存在的true真正含义是什么。

该页面说明Equals true if this atomic type is always lock-free and false if it is never or sometimes lock-free.

那么“这个原子类型总是无锁的”是什么意思呢?

Nat*_*dge 12

这里的“锁”是指“互斥锁”,而不是专门指名为 的 x86 指令前缀lock

实现std::atomic<T>任意类型的一种简单而通用的方法T是作为一个包含T成员和 a 的类std::mutex,该类在对象上的每个操作(加载、存储、交换、fetch_add 等)周围被锁定和解锁。这些操作可以用任何旧的方式完成,并且不需要使用原子机器指令,因为锁可以保护它们。此实现不是无锁的

这种实现的一个缺点是,除了通常速度慢之外,如果两个线程尝试同时操作该对象,其中一个线程将必须等待锁,这实际上可能会阻塞并导致它被调度出去一会儿。或者,如果一个线程在持有锁的情况下被调度出去,则所有其他想要操作该对象的线程都必须等待第一个线程重新调度并首先完成其工作。

因此,希望机器支持真正的原子操作T:其他线程无法干扰的单个指令或序列,并且如果被中断(或者可能根本无法被中断),也不会阻塞其他线程。如果对于某种类型,T库能够专门std::atomic<T>提供这样的实现,那么这就是我们所说的无锁的意思。(这在 x86 上令人困惑,因为用于此类实现的原子指令被命名为lock。在其他体系结构上,它们可能被称为其他名称,例如 ARM64 的ldxr/stxr独占加载/存储指令。)

C++ 标准允许类型“有时无锁”:也许在编译时不知道是否std::atomic<T>会无锁,因为它取决于运行时检测到的特殊机器功能。甚至有可能某些类型的对象std::atomic<T>是无锁的,而另一些则不是。这就是为什么atomic_is_lock_free是函数而不是常数。它检查这个特定对象在这一天是否是无锁的。

然而,对于某些实现来说,可能会出现这样的情况:某些类型可以在编译时保证始终是无锁的。这就是is_always_lock_free用来指示的,注意它是一个constexpr bool而不是一个函数。

  • @MohammadRahimi:cppreference 表明,如果无锁原子性需要比“alignof(std::atomic&lt;T&gt;)”更多的额外对齐,则可能会发生这种情况。在这种情况下,某些对象可能碰巧落在足够对齐的地址上,因此能够无锁访问,而那些不太幸运的对象则需要锁。 (3认同)