我认为两者都在做相同的工作,您如何决定使用哪个同步呢?
最佳答案
理论
从理论上讲,当一个线程试图锁定一个互斥锁而没有成功时,由于该互斥锁已被锁定,它将进入睡眠状态,并立即允许另一个线程运行。它会继续睡眠直到被唤醒为止,一旦互斥量被之前持有该锁的任何线程解锁,情况就是如此。当线程尝试锁定自旋锁而没有成功时,它将不断重试对其进行锁定,直到最终成功;否则,它将继续尝试锁定自旋锁。因此,它将不允许其他线程代替它(但是,一旦超出当前线程的CPU运行时范围,操作系统将强制切换到另一个线程)。
问题
互斥锁的问题在于使线程进入睡眠状态并再次唤醒它们都是相当昂贵的操作,它们将需要大量的CPU指令,因此也需要一些时间。如果现在互斥锁仅锁定了很短的时间,则使线程进入睡眠状态并再次唤醒它所花费的时间可能会超过该线程实际睡眠的时间,甚至可能超过该线程将要休眠的时间。不断轮询自旋锁浪费了时间。另一方面,对自旋锁进行轮询将不断浪费CPU时间,如果将锁保持更长的时间,则将浪费更多的CPU时间,而如果线程正在睡眠,那就更好了。
解决方案
在单核/单CPU系统上使用自旋锁通常是没有意义的,因为只要自旋锁轮询阻止了唯一可用的CPU内核,其他线程就无法运行,并且由于没有其他线程可以运行,因此锁不会被解锁。 IOW,自旋锁只会浪费那些系统上的CPU时间,而没有真正的好处。如果改为让该线程进入睡眠状态,则另一个线程可能会立即运行,可能会解除锁定,然后在第一个线程再次唤醒后允许其继续处理。
在多核/多CPU系统上,只有大量锁定仅在很短的时间内保持不变,浪费时间不断使线程进入睡眠状态并再次唤醒它们可能会显着降低运行时性能。相反,当使用自旋锁时,线程有机会利用其完整的运行时域(总是仅在很短的时间段内阻塞,然后立即继续其工作),从而提高了处理吞吐量。
练习
由于程序员通常无法事先知道互斥或自旋锁是否会更好(例如,因为目标体系结构的CPU内核数量未知),因此操作系统也无法知道某个代码段是否已针对单核或UNIX优化。在多核环境中,大多数系统并不严格区分互斥锁和自旋锁。实际上,大多数现代操作系统都具有混合互斥锁和混合自旋锁。这实际上是什么意思?
混合互斥锁首先在多核系统上表现得像自旋锁。如果线程无法锁定互斥锁,则不会立即将其置于睡眠状态,因为互斥锁可能很快就会被解锁,因此互斥锁将首先完全像自旋锁一样工作。只有在一定时间(或重试或任何其他测量因素)之后仍未获得锁定时,线程才真正进入睡眠状态。如果相同的代码在只有一个内核的系统上运行,则互斥锁将不会自旋锁,但是,如上所述,这将无济于事。
混合自旋锁起初的行为类似于普通自旋锁,但为避免浪费过多的CPU时间,它可能有一个后退策略。它通常不会使线程进入睡眠状态(因为您不希望在使用自旋锁时发生这种情况),但是它可能决定停止线程(立即或在一定时间后停止)并允许另一个线程运行,因此增加了自旋锁被解锁的机会(纯线程切换通常比使线程进入睡眠状态并稍后唤醒它的便宜,尽管目前为止还不算很贵)。
摘要
如果有疑问,请使用互斥锁,它们通常是更好的选择,如果这看起来是有益的,大多数现代系统将允许它们在很短的时间内自旋。使用自旋锁有时可以提高性能,但是只有在某些条件下,并且您有疑问才可以告诉我,您目前不在从事自旋锁可能有益的任何项目。您可能会考虑使用自己的“锁对象”,该对象可以在内部使用自旋锁或互斥锁(例如,创建此类对象时可以配置此行为),最初在各处使用互斥锁,如果您认为在某个地方使用自旋锁可能确实帮助,请尝试一下并比较结果(例如,使用探查器),但一定要先测试单核和多核两种情况,然后再得出结论(如果使用代码,则可能使用不同的操作系统)将是跨平台的)。
更新:iOS警告
实际上,不是特定于iOS的平台,而是大多数开发人员可能会遇到此问题的平台:如果您的系统具有线程调度程序,则不能保证任何线程(无论其优先级多么低)最终都有机会运行,那么自旋锁会导致永久性的死锁。 iOS调度程序会区分线程的不同类别,并且仅当高级类别中的任何线程都不希望运行时,低级类别的线程才会运行。没有任何退避策略,因此,如果您永久拥有高级线程,则低级线程将永远不会获得任何CPU时间,因此也就不会有任何机会执行任何工作。
问题出现如下:您的代码在一个低prio类线程中获得了一个自旋锁,当它位于该锁的中间时,时间段已超过,该线程停止运行。再次释放该自旋锁的唯一方法是,如果该低prio类线程再次获得CPU时间,但是不能保证会发生这种情况。您可能有几个高级prio类线程,它们总是想要运行,任务调度程序将始终对它们进行优先级排序。其中之一可能会碰到自旋锁并尝试获取它,这当然是不可能的,并且系统会使其屈服。问题是:产生的线程立即可以再次运行!具有比持有锁的线程更高的优先级,持有锁的线程没有机会获得CPU运行时。其他某个线程将获得运行时或刚刚产生的线程。
为什么互斥锁不会出现此问题?当高prio线程无法获取互斥体时,它不会屈服,它可能会旋转一点,但最终会进入睡眠状态。休眠线程在事件(例如,事件)唤醒之前无法运行。互斥锁正在解锁等事件一直在等待。 Apple已意识到该问题,因此已弃用OSSpinLock
。新锁称为os_unfair_lock
。此锁避免了上面提到的情况,因为它知道不同的线程优先级。如果您确定在您的iOS项目中使用自旋锁是个好主意,请使用该锁。远离OSSpinLock
!而且在任何情况下都不能在iOS中实现自己的自旋锁!如有疑问,请使用互斥锁! macOS不受此问题的影响,因为它具有不同的线程调度程序,该调度程序不允许任何线程(甚至是低prio线程)在CPU时间上“干运行”,在那里仍然可能出现相同的情况,从而导致性能很差性能,因此macOS上也不建议使用OSSpinLock
。