std::atomic::is_always_lock_free = true 到底是什么意思?
What does std::atomic::is_always_lock_free = true really mean?
我有以下代码:
#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;
}
它编译了,所以它意味着 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
很多帖子指出读-修改-写操作(这里fetch_add()
)不能是没有锁的原子操作。
我的问题是 std::atomic::is_always_lock_free
成为 true
的真正含义。
This page 表示 Equals true if this atomic type is always lock-free and false if it is never or sometimes lock-free.
那么“这种原子类型总是无锁的”是什么意思?
这里的“锁”是指“互斥锁”的意思,并不是特指名为lock
.
的x86指令前缀
为任意类型 T
实现 std::atomic<T>
的简单而通用的方法是 class 包含 T
成员和 std::mutex
,它在对象的每个操作(加载、存储、交换、fetch_add 等)周围被锁定和解锁。这些操作可以用任何旧的方式完成,不需要使用原子机器指令,因为锁保护它们。此实现将不锁定。
这种实现的一个缺点,除了一般来说很慢之外,如果两个线程试图同时操作对象,其中一个将不得不等待锁,这实际上可能会阻塞和导致它被安排了一段时间。或者,如果一个线程在持有锁的同时被调度出去,所有其他想要对该对象进行操作的线程都必须等待第一个线程被调度回来并首先完成它的工作。
因此,如果机器支持对 T
的真正原子操作是可取的:其他线程不能干扰的单个指令或序列,并且如果被中断(或者可能无法被中断)也不会阻塞其他线程根本)。如果对于某种类型 T
库已经能够专门化 std::atomic<T>
这样的实现,那么这就是我们所说的 无锁 的意思。 (这在 x86 上令人困惑,因为用于此类实现的原子指令被命名为 lock
。在其他体系结构上,它们可能被称为其他名称,例如 ARM64 的 ldxr/stxr
独占 load/store 指令。)
C++ 标准允许类型“有时无锁”:可能在编译时不知道 std::atomic<T>
是否无锁,因为它取决于将被检测到的特殊机器功能在运行时。甚至有可能某些 std::atomic<T>
类型的对象是无锁的,而另一些则不是。这就是为什么 atomic_is_lock_free
是一个函数而不是一个常量。它检查这个特定对象在这一天是否是无锁的。
但是,对于某些实现,某些类型在编译时可以保证始终是无锁的,这可能是这种情况。就是用is_always_lock_free
表示的,注意是constexpr bool
而不是函数
我有以下代码:
#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;
}
它编译了,所以它意味着 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
很多帖子指出读-修改-写操作(这里fetch_add()
)不能是没有锁的原子操作。
我的问题是 std::atomic::is_always_lock_free
成为 true
的真正含义。
This page 表示 Equals true if this atomic type is always lock-free and false if it is never or sometimes lock-free.
那么“这种原子类型总是无锁的”是什么意思?
这里的“锁”是指“互斥锁”的意思,并不是特指名为lock
.
为任意类型 T
实现 std::atomic<T>
的简单而通用的方法是 class 包含 T
成员和 std::mutex
,它在对象的每个操作(加载、存储、交换、fetch_add 等)周围被锁定和解锁。这些操作可以用任何旧的方式完成,不需要使用原子机器指令,因为锁保护它们。此实现将不锁定。
这种实现的一个缺点,除了一般来说很慢之外,如果两个线程试图同时操作对象,其中一个将不得不等待锁,这实际上可能会阻塞和导致它被安排了一段时间。或者,如果一个线程在持有锁的同时被调度出去,所有其他想要对该对象进行操作的线程都必须等待第一个线程被调度回来并首先完成它的工作。
因此,如果机器支持对 T
的真正原子操作是可取的:其他线程不能干扰的单个指令或序列,并且如果被中断(或者可能无法被中断)也不会阻塞其他线程根本)。如果对于某种类型 T
库已经能够专门化 std::atomic<T>
这样的实现,那么这就是我们所说的 无锁 的意思。 (这在 x86 上令人困惑,因为用于此类实现的原子指令被命名为 lock
。在其他体系结构上,它们可能被称为其他名称,例如 ARM64 的 ldxr/stxr
独占 load/store 指令。)
C++ 标准允许类型“有时无锁”:可能在编译时不知道 std::atomic<T>
是否无锁,因为它取决于将被检测到的特殊机器功能在运行时。甚至有可能某些 std::atomic<T>
类型的对象是无锁的,而另一些则不是。这就是为什么 atomic_is_lock_free
是一个函数而不是一个常量。它检查这个特定对象在这一天是否是无锁的。
但是,对于某些实现,某些类型在编译时可以保证始终是无锁的,这可能是这种情况。就是用is_always_lock_free
表示的,注意是constexpr bool
而不是函数