与' std::mutex '同步比与' std::atomic(memory_order_seq_cst) '

Is synchronizing with `std::mutex` slower than with `std::atomic(memory_order_seq_cst)`?

本文关键字:std seq order cst mutex 同步 atomic memory      更新时间:2023-10-16

使用原子而不是互斥锁的主要原因是互斥锁很昂贵,但是atomics的默认内存模型是memory_order_seq_cst,这不是同样昂贵吗?

问题:有锁的并发程序和无锁的并发程序一样快吗?

如果是,它可能不值得努力,除非我想使用memory_order_acq_rel原子。


编辑:我可能错过了一些东西,但基于锁不能比无锁更快,因为每个锁也必须是一个完整的内存屏障。但是对于无锁,可以使用比内存屏障限制更少的技术。

所以回到我的问题,无锁是否比基于默认memory_model的新c++ 11标准中的锁更快?

"无锁>=基于锁的性能测量"是真的吗?假设有2个硬件线程。


编辑2:我的问题不是关于进度保证的,也许我是断章取义地使用"无锁"。

基本上,当你有两个线程共享内存,你唯一需要的保证是,如果一个线程正在写,那么另一个线程不能读或写,我的假设是一个简单的原子compare_and_swap操作将比锁定互斥锁快得多。

因为如果一个线程甚至从来没有接触过共享内存,那么你将无缘无故地一遍又一遍地锁定和解锁,但是对于原子操作,你每次只使用1个CPU周期。

关于注释,当争用很少时,自旋锁和互斥锁是非常不同的。

无锁编程是关于的进度保证:从最强到最弱依次是无等待无锁无阻塞阻塞

保证是昂贵的,是有代价的。你想要的保证越多,你付的钱就越多。通常,阻塞算法或数据结构(比如带有互斥锁)具有最大的自由度,因此可能是最快的。另一个极端的无等待算法必须在每一步都使用原子操作,这可能会慢得多。

获得锁实际上是相当便宜的,所以在没有深入了解这个主题之前,您不应该担心这个问题。此外,使用互斥锁的阻塞算法更易于阅读、编写和推理。相比之下,即使是最简单的无锁数据结构也是长期专注研究的结果,每一个都值得一个或多个博士学位。

简而言之,锁或无等待算法用最差延迟换取平均延迟和吞吐量。一切都变慢了,但没有什么是非常慢的。这是一个非常特殊的特性,只在非常特定的情况下(如实时系统)有用。

锁往往比简单的原子操作需要更多的操作。在最简单的情况下,memory_order_seq_cst的速度大约是锁定的两倍,因为锁定在其实现中往往至少需要两个原子操作(一个用于锁定,一个用于解锁)。在许多情况下,需要的甚至不止这些。然而,一旦您开始利用内存顺序,它可以更快,因为您愿意接受更少的同步。

同样,你会经常看到"锁定算法总是和无锁算法一样快。"这在某种程度上是正确的。基本思想是,如果最快的算法碰巧是无锁的,那么没有无锁保证的最快算法也是相同的算法!然而,如果最快的算法需要锁,那么那些要求无锁保证的人就不得不去找一个更慢的算法。

通常,您会在一些低级算法中看到无锁算法,在这些算法中,利用专用操作码的性能会有所帮助。在几乎所有其他代码中,锁的性能非常令人满意,而且更易于阅读。

问题:使用锁并发程序的速度能和并发无锁程序?

它可以更快:无锁算法必须始终保持全局状态在一致状态,并且在不知道它们是否有效的情况下进行计算,因为在计算完成时状态可能已经改变,使其无关,从而丢失CPU周期。

无锁策略使序列化发生在进程结束时,当计算完成时。在病态的情况下,许多线程可以执行一个任务,并且只有一个任务是有效的,其他线程将重试。

不管线程的优先级是什么,自由锁会导致某些线程耗尽,并且没有办法避免这种情况。(尽管线程不太可能长时间饿死重试,除非存在疯狂的争用。) 另一方面,"基于串行化计算和一系列副作用"(即基于锁的)算法在知道不会被其他参与者阻止对特定锁定资源进行操作之前不会启动(通过使用互斥锁提供保证)。请注意,如果使用了多个锁,则由于需要访问另一个资源,它们可能无法完成,从而导致在设计糟糕的程序中需要多个锁时可能出现死锁。

注意这个死锁问题不在无锁代码的范围内,它甚至不能对多个实体起作用:它通常不能基于两个不相关的对象(1)执行原子提交。

所以无锁代码没有死锁的机会是无锁代码的弱点的标志:不能死锁是你的工具的限制。一个只能同时持有锁的系统也不能死锁。

与基于锁的算法相比,无锁算法的作用域很小。对于很多问题,无锁甚至没有意义。

一个基于锁的算法是礼貌的,线程在做它们需要做的事情之前必须排队等待:这是在每个线程的计算步骤方面最有效的。但是,必须在等待列表中排队的线程效率很低:它们通常无法使用时间片的末端,因此效率非常低,因为有人试图在一直被电话打断的情况下做严肃的工作:他的注意力已经消失,他永远无法达到最高效率,因为他的工作时间被分割成小块。

(1)你会有至少需要能够做一个双CAS,这是一个操作原子在两个任意地址(不是一个双字CAS,这只是一个CAS在更多的位,这可以很容易地实现到自然CPU内存访问仲裁单元,是缓存线)。