java无锁算法,多线程-无锁算法的性能真的比全锁算法好吗?

多线程-无锁算法的性能真的比全锁算法好吗?

雷蒙德·陈(Raymond Chen)在无锁算法方面进行了大量研究。 除了InterlockedXxx函数的简单情况之外,似乎所有这些方法的主流模式是它们实现自己的锁。 当然,没有处理器锁,但是在每个CPU上不断循环以确保一致性的概念非常像自旋锁。 作为自旋锁,它们将比操作系统附带的常规锁效率更低,因为它们在等待其他线程时不会产生对量子的控制。 因此,每当有人来找我说“但是我的算法是无锁的”时,我的一般回答是“是”?

我很好奇-是否有基准可以显示无锁算法比全锁算法有优势?

10个解决方案

27 votes

通常,无锁算法对每个线程的效率较低-正如您提到的,与简单锁相比,要实现无锁算法,您需要做更多的工作。

但是,面对竞争,它们确实确实可以显着提高整个算法的整体吞吐量。 线程切换延迟和上下文切换会在许多线程上快速运行,从而极大地降低了应用程序的吞吐量。 无锁算法有效地实现了它们自己的“锁”,但是这样做的方式是防止或减少上下文切换的数量,这就是为什么它们倾向于不执行其对应的锁的原因。

话虽这么说-大多数取决于所讨论的算法(和实现)。 例如,我有一些例程可以设法切换到.NET 4的新并发集合,而不是使用以前的锁定机制,并且在我的总算法速度上测得的改进超过30%。 话虽如此,与基本锁相比,您可以找到许多基准测试,这些测试使用某些相同的集合显示性能降低。 与所有性能优化一样,您只有在进行测量时才真正知道。

Reed Copsey answered 2020-07-07T23:20:47Z

17 votes

除了InterlockedXxx函数的简单情况外,似乎 所有这些的普遍模式是它们实施 自己的锁。

此处似乎没有一个答案真正成为“无锁” CAS循环与互斥锁或自旋锁之间区别的核心。

重要的区别在于,无需其他线程的帮助,就可以保证无锁算法能够自己取得进步。 使用锁或自旋锁,任何无法获取锁的不良线程将完全由拥有该锁的线程决定。 除了等待(通过繁忙的等待或操作系统辅助的睡眠)之外,无法获取锁定的不良线程无法执行任何操作。

使用在CAS上循环的无锁算法,无论其他竞争线程在做什么,都可以确保每个线程都取得进展。 每个线程本质上都在控制自己的命运。 是的,它可能仍然必须循环很多次,但是循环的次数受竞争线程数的限制。 在大多数情况下,它不能无限循环。 (在实践中,由于例如LL / SC循环由于错误共享而导致失败,可能会发生实时锁定)-但线程本身也可以采取措施来解决此问题-绝非易事 另一个锁的线程。

至于性能,这取决于。 我已经看到,即使在高线程争用中,无锁算法的公然示例也完全比其无锁算法的性能好。 在运行Debian 7的x86-64机器上,我比较了C ++ Boost.Lockfree队列(基于Michael / Scott算法)和std::queue周围纯净的旧std::queue的性能。在高线程争用情况下,无锁版本几乎 慢一倍。

那为什么呢? 好吧,无锁算法的性能最终取决于实现细节。 该算法如何避免ABA? 它如何完成安全的内存回收? 有很多变体...标记指针,基于时期的回收,RCU /静态,危害指针,常规的过程范围内的垃圾收集等。所有这些策略都对性能产生影响,并且某些策略还对应用程序的总体设置产生了限制。 可以设计。 通常,根据我的经验,引用计数方法(或标记指针方法)的性能往往较差。 但是替代方案的实现可能要复杂得多,并且需要基于线程本地存储或广义垃圾回收的更多内存回收基础结构。

Charles Salvia answered 2020-07-07T23:21:33Z

11 votes

无锁不一定会更快,但是它可以消除死锁或活锁的可能性,因此您可以保证程序将始终在完成过程中取得进展。 使用锁,很难做出任何保证-错过可能导致死锁的某些执行序列太容易了。

除此之外,这一切都取决于。 至少根据我的经验,速度差异往往更多地取决于实现中部署的技能水平,而不是是否使用锁。

Jerry Coffin answered 2020-07-07T23:21:57Z

5 votes

在x64上的Windows下,一个简单的(没有在自由列表前面的组合数组)无锁定的自由列表大约比基于互斥锁的自由列表快一个数量级。

在我的笔记本电脑(Core i5)上,对于单线程来说,无锁状态下我每秒可获得约3100万个freelist操作,而对于互斥锁则为每秒230万左右。

对于两个线程(在单独的物理内核上),使用无锁功能时,每个线程获得约1,240万个自由列表操作。 使用互斥锁,我每秒可以进行约80千次操作。

answered 2020-07-07T23:22:27Z

1 votes

无锁算法绝对可以比阻塞算法更快。 但是当然反之亦然。 假设实现比锁定计数器部分更好,则唯一的限制因素是竞争。

采取两个Java类ConcurrentLinkedQueue和LinkedBlockingQueue。 在适度的实际竞争中,CLQ的性能要比LBQ高很多。 在争用激烈的情况下,使用挂起线程将使LBQ表现更好。

我不同意user237815。 同步关键字不需要像以前那样多的开销,但是相对于无锁算法,与单个CAS相比,它确实具有大量的开销。

John Vint answered 2020-07-07T23:22:56Z

1 votes

真正的无锁算法的主要优点是,即使任务被搁置,它们也很健壮(请注意,与“不使用锁”(*)相比,无锁是更严格的条件)。 尽管避免不必要的锁定具有性能优势,但性能最佳的数据结构通常是那些可以在许多情况下进行锁定但可以使用锁定来最大程度地减少抖动的数据结构。

(*)我已经看到过一些尝试来“锁定”多生产者队列,在这种情况下,在错误的时间放行的生产者将阻止消费者在完成工作之前看到任何新物品); 这样的数据结构不应该真正被称为“无锁”。 一个被阻止的生产者不会阻止其他生产者取得进步,但可以任意地阻止消费者。

supercat answered 2020-07-07T23:23:22Z

0 votes

至少在Java中,锁定本身非常快。 synced关键字不会增加很多开销。 您可以通过在循环中调用同步方法自己对它进行基准测试。

只有在发生争用时,锁定才会变慢,并且被锁定的过程不是瞬时的。

ccleve answered 2020-07-07T23:23:46Z

0 votes

最近,在JavaOne上,俄罗斯Oracle员工(专门研究Java性能和基准)显示了一些有关使用CAS(实际上是无锁,高级自旋锁)和经典锁(java)对简单int计数器进行并行访问时每秒操作的度量。 .util.concurrent.locks.ReentrantLock)

[http://dl.dropbox.com/u/19116634/pics/lock-free-vs-locks.png] //对不起,我无法粘贴图像

据此,自旋锁只有在少数线程尝试访问监视器之前才具有更好的性能。

leventov answered 2020-07-07T23:24:15Z

0 votes

无锁还具有不睡觉的优点。 内核中有一些地方不允许您进入睡眠状态-Windows内核中有很多地方-并严重限制了您使用数据结构的能力。

answered 2020-07-07T23:24:36Z

0 votes

是的,无锁可确保进度,但是除非您手动中止某些平台上可能发生的线程或在关键部分分配线程,并退出内存异常或类似此类的愚蠢行为,否则不需要。如果执行效果不佳,则正确实施的自旋锁几乎总会击败无锁方法,因为通常您会第一次或在尝试不成功后进行更多工作。如果您使用比较交换指令使旋转时间短并且使cpus不堪重负和/或在给线程时间片赋予其他线程一段时间后不退缩(这给了计划外线程唤醒和释放锁的机会),那么无锁代码的性能会更好 。 除此之外,我认为这是不可能的。 我既不感兴趣,也没有兴趣使用自旋锁不适合的复杂数据类型,但我仍然认为正确设计的基于锁的算法几乎总是会更好。 我可能是错的。

TakeMeAsAGuest answered 2020-07-07T23:24:57Z

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值