std::atomic 是如何实现的

Yve*_*ves 4 c++ multithreading mutex lock-free stdatomic

我正在研究C++11 中mutex和之间的区别atomic

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

对于std::atomic,我知道它是基于C++11引入的内存模型实现的。但是,我不知道内存模型在低级别是如何实现的。如果也是通过原子操作之类CompareAndSet的方式实现的,std::atomic和 和 有mutex什么区别?

总之,如果std::atomic::is_lock_free给我一个false,好吧,我会说这std::atomicmutex. 但是如果它给了我一个true,它是如何在低级别实现的?

Bee*_*ope 6

std::atomic和互斥体有什么区别

互斥体是一种并发构造,独立于任何用户数据、产品lockunlock方法,允许您保护代码区域(在其中强制互斥)。你可以在该区域放置任何你想要的东西。

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

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

std::atomic存在主要是因为其他常见的实现:使用原子指令2直接执行操作而不需要互斥体。std::atomic<T>::is_lock_free()这是返回 true时使用的实现。这通常比互斥方法更有效,但仅适用于足够小的对象,可以通过原子指令“一次性”操作。


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

例如,在 x86 上,编译器通过普通加载实现所有std::atomic足够小的值的加载,并实现比memory_order_seq_cst普通存储弱的所有存储。seq_cst然而,存储是通过特殊指令实现的 - 10.1 之前的 GCC 上的尾随,以及 clang、最近的 GCC 和其他编译器上的(隐mfence式)。movlockxchg mem,reg

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


Dav*_*rtz 6

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

不同之处在于,无锁的原子操作没有“锁定”状态。让我们比较两种可能的方法来进行变量的原子增量:

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

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

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

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

  • 我对此添加了一些说明,特别是关于“缓存锁”,它使用与软件互斥/自旋锁锁定相同的词,但“非常”不同。相关:[num++ 对于“int num”可以是原子的吗?](//stackoverflow.com/q/39393850)。另外为了扩大范围:请注意,[LL/SC 机器](https://en.wikipedia.org/wiki/Load-link/store-conditional)(几乎所有主要的非 x86 ISA)都使用“存储条件” *检查*我们是否保持独占所有权,而不是在开始时获取缓存锁。以损失重试吞吐量为代价来减少其他内核的最坏情况延迟。 (2认同)