小编典典

什么时候应该使用自旋锁而不是互斥锁?

all

我认为两者都在做同样的工作,你如何决定使用哪一个进行同步?


阅读 106

收藏
2022-04-01

共1个答案

小编典典

理论

理论上,当一个线程试图锁定一个互斥体并且它没有成功时,因为互斥体已经被锁定,它会进入睡眠状态,立即允许另一个线程运行。它将继续休眠直到被唤醒,一旦互斥锁被之前持有锁的任何线程解锁,就会出现这种情况。当一个线程试图加锁一个自旋锁但没有成功时,它会不断地重试加锁,直到最终成功;因此它不会允许另一个线程取代它的位置(当然,一旦超过当前线程的
CPU 运行时间量子,操作系统将强制切换到另一个线程)。

问题

互斥锁的问题在于,让线程进入睡眠状态并再次唤醒它们都是相当昂贵的操作,它们需要相当多的 CPU
指令,因此也需要一些时间。如果现在互斥锁只被锁定了很短的时间,那么让线程进入睡眠状态并再次唤醒它所花费的时间可能会超过线程实际睡眠的时间,甚至可能超过线程的睡眠时间。通过不断地轮询自旋锁而浪费了。另一方面,对自旋锁进行轮询会不断浪费
CPU 时间,如果锁被持有的时间更长,这将浪费更多的 CPU 时间,如果线程处于休眠状态会更好。

解决方案

在单核/单 CPU 系统上使用自旋锁通常没有意义,因为只要自旋锁轮询阻塞唯一可用的 CPU
内核,就没有其他线程可以运行,并且由于没有其他线程可以运行,锁就不会也可以解锁。IOW,自旋锁只会在这些系统上浪费 CPU
时间而没有真正的好处。如果线程改为休眠,另一个线程可能会立即运行,可能会解锁锁,然后允许第一个线程继续处理,一旦它再次醒来。

在多核/多 CPU
系统上,大量的锁只持有很短的时间,浪费在不断地让线程休眠并再次唤醒它们的时间可能会显着降低运行时性能。当使用自旋锁代替时,线程有机会利用其完整的运行时量子(总是只阻塞很短的时间段,但随后立即继续工作),从而导致更高的处理吞吐量。

实践

由于程序员经常无法提前知道互斥锁或自旋锁是否会更好(例如,因为目标架构的 CPU
内核数量未知),操作系统也无法知道某段代码是否已针对单核或自旋锁进行了优化。在多核环境中,大多数系统并没有严格区分互斥锁和自旋锁。事实上,大多数现代操作系统都有混合互斥锁和混合自旋锁。这实际上是什么意思?

混合互斥锁起初在多核系统上的行为类似于自旋锁。如果线程无法锁定互斥锁,它不会立即进入睡眠状态,因为互斥锁可能很快就会解锁,因此互斥锁首先会像自旋锁一样工作。只有在经过一定时间(或重试或任何其他测量因素)后仍未获得锁时,线程才真正进入睡眠状态。如果相同的代码在只有一个核心的系统上运行,互斥锁将不会自旋锁,但是,如上所示,这不会有好处。

混合自旋锁起初的行为类似于普通自旋锁,但为了避免浪费过多的 CPU
时间,它可能具有退避策略。它通常不会让线程进入睡眠状态(因为您不希望在使用自旋锁时发生这种情况),但它可能会决定停止线程(立即或在一定时间后;这称为“屈服”
) 并允许另一个线程运行,从而增加了自旋锁被解锁的机会(您仍然有线程切换的成本,但没有让线程休眠并再次唤醒它的成本)。

概括

如果有疑问,请使用互斥锁,它们通常是更好的选择,并且大多数现代系统将允许它们在很短的时间内自旋锁定,如果这看起来有益的话。使用自旋锁有时可以提高性能,但只有在某些条件下,并且您有疑问的事实告诉我,您目前没有从事自旋锁可能有益的任何项目。您可能会考虑使用自己的“锁定对象”,它可以在内部使用自旋锁或互斥锁(例如,在创建此类对象时可以配置此行为),最初在任何地方都使用互斥锁,如果您认为在某处使用自旋锁可能真的帮助,试一试并比较结果(例如使用分析器),但一定要测试这两种情况,

更新:iOS 警告

实际上不是 iOS 特定的,但 iOS
是大多数开发人员可能面临这个问题的平台:如果您的系统有一个线程调度程序,这并不能保证任何线程,无论其优先级有多低,最终都会有机会运行,那么自旋锁会导致永久死锁。iOS调度器区分不同类的线程,低类的线程只有在高类中没有线程想要运行的情况下才会运行。对此没有退避策略,因此如果您永久拥有可用的高级线程,则低级线程将永远不会获得任何
CPU 时间,因此永远不会有机会执行任何工作。

问题出现如下:您的代码在低优先级线程中获得了一个自旋锁,当它处于该锁的中间时,已超过时间量并且线程停止运行。再次释放此自旋锁的唯一方法是,如果该低优先级线程再次获得
CPU
时间,但这并不能保证会发生。您可能有几个高优先级的线程,它们不断地想要运行,并且任务调度程序总是会优先考虑这些线程。其中一个可能会跑过自旋锁并尝试获取它,这当然是不可能的,系统会使其屈服。问题是:产生的线程可以立即再次运行!拥有比持有锁的线程更高的优先级,持有锁的线程没有机会获得
CPU 运行时间。

为什么互斥锁不会出现这个问题?当高优先级线程无法获得互斥体时,它不会屈服,它可能会旋转一点,但最终会被送入休眠状态。一个休眠的线程在被一个事件唤醒之前是不能运行的,例如,一个它一直在等待的互斥锁被解锁的事件。Apple
已意识到该问题,因此已弃用OSSpinLock。新锁称为os_unfair_lock。这个锁避免了上面提到的情况,因为它知道不同的线程优先级。如果您确定在您的
iOS 项目中使用自旋锁是一个好主意,请使用那个。保持距离OSSpinLock!在任何情况下都不要在 iOS
中实现您自己的自旋锁!如果有疑问,请使用互斥锁。macOS 不受此问题的影响,因为它有一个不同的线程调度程序,它不允许任何线程(即使是低优先级线程)在
CPU 时间上“干涸”,仍然可能出现同样的情况,然后导致非常糟糕性能,因此OSSpinLock在 macOS 上也被弃用。

2022-04-01