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

Synchronization 什么时候应该使用自旋锁而不是互斥锁?,synchronization,mutex,spinlock,Synchronization,Mutex,Spinlock,我认为两者都在做相同的工作,如何决定使用哪一个进行同步?理论 理论上,当一个线程试图锁定一个互斥锁但没有成功时,因为互斥锁已经被锁定,它将进入睡眠状态,立即允许另一个线程运行。它将继续休眠直到被唤醒,这将是一旦互斥锁被之前持有锁的线程解锁时的情况。当线程尝试锁定自旋锁但未成功时,它将持续重新尝试锁定它,直到最终成功;因此,它将不允许另一个线程取代它的位置(当然,一旦超过当前线程的CPU运行时数量,操作系统将强制切换到另一个线程) 问题 互斥锁的问题是,让线程进入睡眠状态并再次唤醒它们都是非常昂贵

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

理论

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

问题

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

解决方案

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

在多核/多CPU系统上,由于大量锁只能在很短的时间内保持,因此持续将线程置于睡眠状态并再次唤醒线程所浪费的时间可能会显著降低运行时性能。当使用自旋锁时,线程有机会利用其完整的运行时量(总是只在很短的时间内阻塞,但随后立即继续工作),从而获得更高的处理吞吐量

实践

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

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

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

摘要

如果有疑问,请使用互斥锁,互斥锁通常是更好的选择,如果这似乎有益的话,大多数现代系统将允许它们在很短的时间内进行自旋锁。使用自旋锁有时可以提高性能,但只有在某些情况下,而且您有疑问这一事实告诉我,您没有从事任何项目当前,一个自旋锁可能是有益的。你可以考虑使用你自己的“锁定对象”,它可以在内部使用自旋锁或互斥体(例如,在创建这样的对象时,这种行为是可配置的)。,最初在任何地方都使用互斥锁,如果您认为在某个地方使用自旋锁可能真的有帮助,请尝试一下并比较结果(例如,使用探查器),但在得出结论之前,一定要测试这两种情况,即单核和多核系统(如果您的代码是跨平台的,则可能测试不同的操作系统)

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

问题如下:您的代码获得一个spinlo