我认为两者都在做相同的工作,您如何确定要使用哪个同步呢?
我认为两者都在做相同的工作,您如何确定要使用哪个同步呢?
Answers:
理论
从理论上讲,当一个线程试图锁定一个互斥锁而没有成功时,由于该互斥锁已经被锁定,它将进入睡眠状态,并立即允许另一个线程运行。它会继续睡眠直到被唤醒为止,一旦互斥量被之前持有该锁的任何线程解锁,情况就是如此。当线程尝试锁定自旋锁而没有成功时,它将不断重试锁定它,直到最终成功;否则,它将继续尝试锁定自旋锁。因此,它将不允许其他线程代替(但是,一旦超出当前线程的CPU运行时范围,操作系统将强制切换到另一个线程)。
问题
互斥锁的问题在于使线程进入睡眠状态并再次唤醒它们都是相当昂贵的操作,它们将需要大量的CPU指令,因此也需要一些时间。如果现在互斥锁仅锁定了很短的时间,则使线程进入睡眠状态并再次唤醒它所花费的时间可能会超过该线程实际睡眠的时间,甚至可能超过该线程将要休眠的时间。不断轮询自旋锁浪费了时间。另一方面,对自旋锁进行轮询将不断浪费CPU时间,如果将锁保持更长的时间,则将浪费更多的CPU时间,而如果线程正在休眠则更好。
解决方案
在单核/单CPU系统上使用自旋锁通常是没有意义的,因为只要自旋锁轮询阻止了唯一可用的CPU内核,其他线程就无法运行,并且由于没有其他线程可以运行,因此锁不会被解锁。IOW,自旋锁只会浪费那些系统上的CPU时间,而没有真正的好处。如果改为让该线程进入睡眠状态,则另一个线程可能会立即运行,可能会解除锁定,然后在第一个线程再次唤醒后允许其继续处理。
在多核/多CPU系统上,只有大量锁定仅在很短的时间内保持不变,浪费时间不断使线程进入睡眠状态并再次唤醒它们可能会显着降低运行时性能。相反,当使用自旋锁时,线程有机会利用其完整的运行时域(总是仅在很短的时间内阻塞,但随后立即继续其工作),从而导致更高的处理吞吐量。
实践
由于程序员通常无法事先知道互斥或自旋锁是否会更好(例如,因为目标体系结构的CPU内核数量未知),因此操作系统也无法知道某个代码段是否已针对单核或Windows优化。在多核环境中,大多数系统并不严格区分互斥锁和自旋锁。实际上,大多数现代操作系统都具有混合互斥锁和混合自旋锁。这实际上是什么意思?
混合互斥锁首先在多核系统上表现得像自旋锁。如果线程无法锁定互斥锁,则不会立即将其置于睡眠状态,因为互斥锁可能很快就会被解锁,因此互斥锁将首先完全像自旋锁一样工作。只有在一定时间(或重试或任何其他测量因素)之后仍未获得锁定时,线程才真正进入睡眠状态。如果相同的代码在只有一个内核的系统上运行,则互斥锁将不会自旋锁,但是,如上所述,这将无济于事。
混合自旋锁起初的行为类似于普通自旋锁,但为避免浪费过多的CPU时间,它可能有一个后退策略。它通常不会使线程进入睡眠状态(因为使用自旋锁时您不希望这种情况发生),但是它可能会决定(立即或在一定时间后)停止线程并允许另一个线程运行,因此增加了自旋锁被解锁的机会(纯线程切换通常比使线程进入睡眠状态并稍后唤醒它的便宜,尽管目前为止还不算很贵)。
摘要
如果有疑问,请使用互斥锁,它们通常是更好的选择,如果这看起来很有用,大多数现代系统将允许它们在很短的时间内自旋。使用自旋锁有时可以提高性能,但是仅在某些条件下,您有疑问的事实告诉我,您目前不在从事自旋锁可能有益的任何项目。您可以考虑使用自己的“锁定对象”,该对象可以在内部使用自旋锁或互斥锁(例如,在创建此类对象时可以配置此行为),最初在各处使用互斥锁,如果您认为在某个地方使用自旋锁可能确实帮助,尝试一下并比较结果(例如使用探查器),但一定要测试两种情况,
实际上,不是特定于iOS的平台,而是大多数开发人员可能会遇到此问题的平台:如果您的系统具有线程调度程序,则不能保证任何线程(无论其优先级多么低)最终都有机会运行,那么自旋锁会导致永久性的死锁。iOS调度程序会区分线程的不同类别,并且仅当高级类别中的任何线程都不希望运行时,低级类别的线程才会运行。没有任何退避策略,因此,如果您永久拥有高级线程,则低级线程将永远不会获得任何CPU时间,因此也就不会有任何机会执行任何工作。
问题出现如下:您的代码在一个低prio类线程中获得了一个自旋锁,当它位于该锁的中间时,时间段已超过,该线程停止运行。再次释放该自旋锁的唯一方法是,如果该低prio类线程再次获得CPU时间,但不能保证会发生这种情况。您可能有几个经常要运行的高级prio类线程,任务计划程序将始终对它们进行优先级排序。其中之一可能会碰到自旋锁并尝试获取它,这当然是不可能的,并且系统会使其屈服。问题是:产生的线程立即可以再次运行!具有比持有锁的线程更高的优先级,持有锁的线程没有机会获得CPU运行时。
为什么互斥锁不会出现此问题?当高prio线程无法获取互斥体时,它不会屈服,它可能会旋转一点,但最终会进入睡眠状态。睡眠线程只有在被事件唤醒后才可以运行,例如,等待互斥锁被解锁的事件。苹果公司意识到了这个问题,因此已弃用OSSpinLock
。新锁称为os_unfair_lock
。此锁避免了上述情况,因为它知道不同的线程优先级。如果您确定在您的iOS项目中使用自旋锁是个好主意,请使用该锁。保持距离OSSpinLock
!而且在任何情况下都不能在iOS中实现自己的自旋锁!如有疑问,请使用互斥锁!macOS不受此问题的影响,因为它具有不同的线程调度程序,该调度程序不允许任何线程(甚至是低prio线程)在CPU时间上“干运行”,在那里仍然可能出现相同的情况,从而导致性能很差性能,因此OSSpinLock
在macOS上也已弃用。
继续Mecki的建议,Alexander Sandler博客上的pthread互斥锁与pthread自旋锁,Linux上的Alex展示了如何使用#ifdef实现spinlock
&mutexes
来测试行为。
但是,请确保根据您的观察进行最终决定,因为给出的示例是一个单独的案例,因此您的项目要求,环境可能完全不同。
另请注意,在某些环境和条件下(例如,在Windows上以> = DISPATCH LEVEL的调度级别运行),您不能使用互斥锁,而是使用自旋锁。在Unix上-同样的事情。
这是竞争对手stackexchange UNIX网站上的等效问题:https : //unix.stackexchange.com/questions/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something-更多
Windows系统上的分派信息:http : //download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc
梅基的答案很好。但是,在单个处理器上,当任务正在等待中断服务例程提供的锁时,使用自旋锁可能很有意义。中断会将控制权转移给ISR,ISR将为等待任务使用的资源做好准备。在将控制权交还给被中断的任务之前,它会先释放锁定。旋转任务将找到可用的自旋锁并继续。
自旋锁和互斥锁同步机制今天非常普遍。
让我们首先考虑一下Spinlock。
基本上,这是一个繁忙的等待操作,这意味着我们必须等待指定的锁释放后才能继续执行下一个操作。从概念上讲非常简单,但实现时并非如此。例如:如果尚未释放锁,则线程被换出并进入睡眠状态,我们应该处理它吗?当两个线程同时请求访问时,如何处理同步锁?
通常,最直观的想法是通过变量处理同步以保护关键部分。Mutex的概念相似,但是仍然不同。专注于:CPU利用率。自旋锁会消耗CPU时间来等待执行操作,因此,我们可以总结两者之间的区别:
在同类多核环境中,如果在关键部分花费的时间少于使用Spinlock,则可以减少上下文切换时间。(单核比较并不重要,因为某些系统在开关中间实现了Spinlock)
在Windows中,使用Spinlock会将线程升级到DISPATCH_LEVEL,在某些情况下可能不允许这样做,因此这次我们不得不使用Mutex(APC_LEVEL)。
在单核/单CPU系统上使用自旋锁通常是没有意义的,因为只要自旋锁轮询阻止了唯一可用的CPU内核,其他线程就无法运行,并且由于没有其他线程可以运行,因此锁不会被解锁。IOW,自旋锁仅浪费那些系统上的CPU时间,没有任何真正的好处
错了 在单处理器系统上使用自旋锁不会浪费cpu周期,因为一旦一个进程获得了自旋锁,就将禁用抢占,因此,就不会有其他人在旋转!只是使用它没有任何意义!因此,Uni系统上的自旋锁在编译时被内核替换为preempt_disable!