[英]How is std::atomic implemented
我正在研究 C++11 中mutex
和atomic
之间的区别。
据我了解, mutex
是一种锁机制,是基于操作系统/内核实现的。 例如,Linux 提供了一种机制,即futex
。 在futex
的帮助下,我们可以实现mutex
和semaphore
。 此外,我知道futex
是由低级原子操作实现的,例如CompareAndSet
、 CompareAndSwap
。
对于std::atomic
,我知道它是基于 C++11 引入的内存模型实现的。 但是,我不知道内存模型在低级别是如何实现的。 如果它也是通过像CompareAndSet
这样的原子操作来实现的,那么std::atomic
和mutex
什么区别?
总之,如果std::atomic::is_lock_free
给我一个false
,那么我会说std::atomic
与mutex
相同。 但是如果它给了我一个true
,它是如何在低级别实现的?
如果原子操作是lock_free
,则它们的实现方式可能与互斥锁组件的实现方式相同。 毕竟,要锁定互斥锁,您确实需要某种原子操作来确保只有一个线程锁定互斥锁。
不同之处在于,无锁的原子操作没有“锁定”状态。 让我们比较两种可能的方法来进行变量的原子增量:
首先,互斥方式。 我们锁定一个互斥锁,我们读-增-写变量,然后解锁互斥锁。 如果线程在读-增-写过程中被中断,则其他尝试执行相同操作的线程将阻止尝试锁定互斥锁。 (请参阅std::atomic 的锁在哪里?了解它在某些实际实现中的工作原理,对于太大而无法 lock_free 的对象。)
第二,原子方式。 CPU 仅“锁定”包含我们要在单个读-增-写指令期间修改的变量的缓存行。 (这意味着 CPU 延迟响应 MESI 请求以使缓存行无效或共享,保持独占访问,因此其他 CPU 无法查看它。MESI 缓存一致性总是需要在内核可以修改缓存行之前独占拥有缓存行,因此这是如果我们已经拥有这条线,那么便宜)。 我们不可能在指令中被打断。 另一个试图访问这个变量的线程,最坏的情况是,必须等待缓存一致性硬件找出谁可以修改内存位置。
那么我们如何锁定互斥锁呢? 可能我们执行原子比较和交换。 所以轻原子操作是组装重互斥操作的原语。
当然,这都是特定于平台的。 但这就是您可能使用的典型现代平台所做的。
std::atomic
和 mutex 有什么区别
互斥体是一种并发构造,独立于任何用户数据,提供lock
和unlock
方法,允许您保护(在其中强制互斥)代码区域。 你可以在那个区域放任何你想要的东西。
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 上mov
之后的尾随mfence
,以及(隐式lock
) xchg mem,reg
在 clang、最近的 GCC 和其他编译器上。
另请注意,加载和存储之间的不对称性是编译器的选择:它们本可以对seq_cst
加载进行特殊处理,但因为在大多数情况下加载通常会超过速度较慢的存储。 (而且因为快速路径中的廉价负载更有价值。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.