std::atomic是如何实现的

How is std::atomic implemented

我正在研究 C++11 中 mutexatomic 的区别。

据我理解,mutex是一种锁机制,是基于OS/kernel实现的。例如,Linux提供了一种机制,即futex。在futex的帮助下,我们可以实现mutexsemaphore。此外,我知道 futex 是由低级原子操作实现的,例如 CompareAndSet, CompareAndSwap.

对于std::atomic,我知道它是基于C++11引入的内存模型实现的。但是,我不知道内存模型是如何在底层实现的。如果也是像CompareAndSet那样用原子操作来实现,那么std::atomicmutex有什么区别呢?

一句话,如果std::atomic::is_lock_free给我一个false,那我就说std::atomicmutex一样。但是如果它给我一个true,它是如何在底层实现的?

what is the difference between std::atomic and mutex

互斥锁是一种并发构造,独立于任何用户数据,提供 lockunlock 方法,允许您保护(在其中强制执行互斥)region 的代码。你可以在那个区域放任何你想要的东西。

std::atomic<T> 类型 T 的单个实例上的适配器,允许在 每个操作的基础上进行原子访问 到那个对象。

std::atomic 的一种可能实现是使用互斥锁保护对底层对象的所有访问的意义上说,互斥锁更为通用。

std::atomic的存在主要是因为other常见的实现方式:使用原子指令2直接执行操作而不用需要一个互斥体。这是 std::atomic<T>::is_lock_free() returns true 时使用的实现。这通常比互斥方法更有效,但仅适用于小到足以被原子指令操作的对象 "in one shot"。


2 在某些情况下,编译器能够使用 plain 指令(而不是特殊的并发相关指令),例如正常加载和商店,如果他们在相关平台上提供所需的保证。

例如,在 x86 上,compilers implement 所有 std::atomic 负载,对于足够小的值和普通负载,并使用普通存储实现所有比 memory_order_seq_cst 弱的存储。 seq_cst 存储是使用特殊指令实现的,但是 - 在 10.1 之前的 GCC mov 之后的尾随 mfence,以及(隐含的 lockxchg mem,reg on clang,最近GCC 和其他编译器。

另请注意,加载和存储之间的不对称是编译器的选择:他们本可以将特殊处理放在 seq_cst 加载上,但因为加载通常比存储多,所以在大多数情况下速度较慢。 (而且因为快速路径中的廉价负载更有价值。)

如果原子操作是 lock_free,它们的实现方式可能与互斥体组件的实现方式相同。毕竟,要锁定互斥量,您确实需要某种原子操作来确保只有一个线程锁定互斥量。

区别在于,无锁的原子操作没有 "locked" 状态。让我们比较两种可能的变量原子增量方式:

首先,互斥方式。我们锁定一个互斥量,我们读取-递增-写入变量,然后我们解锁互斥量。如果线程在读取-增量-写入期间被中断,则尝试执行相同操作的其他线程将阻止尝试锁定互斥量。 (请参阅 以了解它在某些实际实现中的工作原理,对象太大而无法 lock_free。)

二、原子方式。 CPU "locks" 只是缓存行,其中包含我们要在单个读-递增-写指令期间修改的变量。 (这意味着 CPU 延迟响应 MESI 请求使缓存行无效或共享,保持独占访问,因此其他 CPU 无法查看它。MESI 缓存一致性始终需要缓存行的独占所有权一个核心可以修改它,所以如果我们已经拥有这条线,这很便宜)。我们不可能在指令期间被打断。另一个试图访问这个变量的线程,在最坏的情况下,必须等待缓存一致性硬件找出谁可以修改内存位置。

那么我们如何锁定互斥锁呢?可能我们执行原子比较和交换。因此,轻型原子操作是组装重型互斥操作的原语。

当然这都是特定于平台的。但这就是您可能使用的典型现代平台所做的。