自旋锁与轮询有何不同?


41

自旋锁和轮询是同一件事吗?

维基百科:

自旋锁是一种锁,它使试图获取它的线程在循环中反复等待(“自旋”),而反复检查该锁是否可用

这听起来非常像:

while(!ready);

我被教导要避免轮询,因为它是完全次优的。那么,自旋锁是否适合不良的旧民意测验?自旋锁与轮询有何不同?

Answers:


85

轮询是指反复检查资源任何类型的资源)是否准备就绪。

自旋锁是指您要轮询的资源处于锁定状态。

请注意,轮询不错。特别是,在轮询时通常准备好数据时,轮询是有效的。轮询仅在您不进行任何操作而不获取任何数据的情况下才是低效的。

另一方面,如果有太多数据以至于不断被中断,则中断效率很低。如果数据很少到达,以至于您实际上可以在中断之前完成一些有用的工作,它们将非常有效。

我可以从我的经验中给你一个真实的例子:15年前,我设置了我的电子邮件程序,每次收到新电子邮件时都会打扰我。每周发生一两次。经常检查我的收件箱会浪费大量时间。

如今,我已关闭所有通知。我知道,每当我查看收件箱时,都会有新的电子邮件。轮询现在效率更高。

自旋锁在以下情况下非常有效:a)锁被锁的可能性很低,b)如果锁被锁了,那么锁将只保留一小段时间。换句话说:对于大多数无竞争的细粒度锁,它是有效的,但对于高度竞争的粗粒度锁,则效率不高。

(当然,自旋锁仅在存在真正的并行性时才起作用,否则其他线程将没有机会释放该锁。我想这很明显,但是无论如何我都想声明它。)


5
在协作式多任务环境中,自旋锁可能非常明智。您只需要确保在循环中放弃控制即可。
凯文(Kevin)

4
电子邮件的好例子。提醒我您总是有电子邮件 ...
PetrPudlák2015年

2
@Kevin:我对自旋锁有一个非常纯粹的想法,实际上是一个只会在空循环中旋转的锁。(atomically_do { while (lock.is_locked?); lock.acquire! })如果它屈服,那不是纯粹的循环,因此从那个纯粹的观点来看不是自旋锁:-D但是,当然,与其他类型的锁或对柏拉图式理想的放松/增添的杂交在现实世界中是完全合理的。
约尔格W¯¯米塔格

3
@bubakazouba:自旋锁的工作原理是在一个空循环中按字面上“旋转”并检查“该锁是否已释放?该锁是否已释放?该锁是否已释放?该锁是否已释放?该锁是否已释放?” 一遍又一遍地。如果没有并行性,则不会同时运行任何其他可以释放该锁的线程,因此您实际上有一个无限循环!请直接在您的上方查看评论。
约尔格W¯¯米塔格

1
@kasperd:您仍然可以在事件驱动的服务器软件(如nginx)中看到协作式多任务处理。在这种情况下,只有一个线程并且没有锁定,这意味着您只能利用一个内核。据我所知,实际上没有真正的多核协作多任务处理的实际示例。这样的系统将是愚蠢的,因为您将获得协作式多任务处理的所有弊端,而没有无锁的弊端。
凯文

10

自旋锁是一种类型的锁,具体而言,是通过轮询实现的。

轮询是一种检查事物状态的方法(通过询问状态,而不是等待被告知状态)。

并非所有轮询都是自旋锁,例如,轮询键盘按键的状态。


同样,轮询并不是天生的坏事。轮询时间非常短,可以避免使用中断所需的昂贵上下文转移,更不用说轮询有时可能更易于实现,因此更易于维护,尤其是在较低级别。像往常一样,绝对是一件可怕的事情,你应该使用给出了相应的性能/复杂度的权衡适合您需求的方法,你已经测量他们,而不是一味地使用或丢弃选项。


5

自旋锁与轮询不同,因为自旋锁只会在很短的时间内发生(大约几毫秒或更短的时间)。轮询可以无限期进行。

在并行编程中,短暂的旋转通常比阻塞更可取,因为它避免了上下文切换和内核转换的开销。

进一步阅读
C#自旋锁中的SpinLock和SpinWait和C中
的读写锁


我想你是说微秒。对于持续一毫秒的锁,我们应该使用一个锁实现,该锁实现对争用进行内核调用。
彼得

@Peter也许那就是Albahari的意思。这就是他的文字所说的。
罗伯特·哈维

4

不同之处在于(希望)自旋锁仅在适当的情况下使用,并且在这些情况下非常有效。

如果期望资源仅在很短的时间内被锁定(例如,仅用于更新变量的锁定),则可以使用自旋锁。自旋锁以最大速度轮询该锁,但希望少于一微秒。普通互斥锁将需要在OS上调用。如果仅将锁保持一小段时间,那么自旋锁仅占用很少的CPU时间,而OS调用将花费更长的时间。但是,如果这种期望是错误的,则自旋锁效率很低-一个CPU将占用100%的CPU时间,而普通互斥锁仅花费进入操作系统并返回的时间。

有时两者结合在一起。您将运行自旋锁一小段时间,如果自旋锁不起作用,则切换到其他策略。


2

过多的轮询是您不应该执行的操作,因为它浪费了系统资源。因此,如果不浪费系统资源,则轮询是可以的

例如,在实际工作仅导致2%的负载的情况下,过多的轮询将使CPU负载达到100%。

轮询除了可以用于其他用途while(!ready)。例如,这意味着定期检查用户是否按下了按键。一个理智的实现最多每15毫秒检查一次,因此,它是每2000万个时钟周期一次的检查。这种轮询非常好,因为它不会浪费系统资源。

自旋锁不是特殊情况。如果我们有一个SpinLock,并且一个线程进入了锁,而另一个线程必须等待,则当且仅当正在等待的线程浪费了系统资源时,SpinLock是错误的选择。当且仅当等待线程必须等待大量时间才能获取锁时,才会浪费系统资源。

因此,正是出于您所说的原因,使用SpinLock保护需要几千个时钟周期或更长时间才能再次解锁的任何东西(例如,即时编译和执行一段javascript)是不好的。但是使用SpinLock保护快速完成的操作(例如访问正确实现的哈希映射)很好,因为等待线程只会旋转2到3次,因此不会浪费系统资源。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.