当前位置:  开发笔记 > 编程语言 > 正文

Spinlock与Semaphore

如何解决《Spinlock与Semaphore》经验,为你挑选了7个好方法。

信号量和自旋锁之间的基本区别是什么?

什么时候我们会使用信号量而不是自旋锁?



1> Damon..:

Spinlock和信号量主要有四个不同之处:

1.他们是什么
一个自旋锁是一个可能实现的锁,即一个由忙等待("旋转")来实现的.信号量是锁的推广(或者,相反,锁是信号量的特殊情况).通常,但不一定,自旋锁仅在一个进程内有效,而信号量也可用于在不同进程之间进行同步.

锁可用于互斥,即一次一个线程可以获取锁并继续执行代码的"关键部分".通常,这意味着修改多个线程共享的某些数据的代码.
一个信号有一个计数器,并允许自己被收购一个或多个线程,这取决于你发布什么价值给它,(在一些实现),这取决于它的最大允许值是什么.

在这种情况下,可以将锁定为信号量的特殊情况,其最大值为1.

2.他们做了什么
如上所述,自旋锁是锁,因此是互斥(严格1对1)机制.它通过重复查询和/或修改存储器位置来工作,通常以原子方式.这意味着获取自旋锁是一种"繁忙"操作,可能会长时间(可能永远!)消耗CPU周期,同时它有效地实现"无需".
这种方法的主要动机是上下文切换具有相当于旋转几百(或几千)次的开销,因此如果通过燃烧几个周期旋转可以获得锁定,这可能总体上很好更高效.此外,对于实时应用程序,阻止和等待调度程序在将来某个遥远的时间返回它们可能是不可接受的.

相比之下,信号量要么根本不旋转,要么只旋转很短的时间(作为优化以避免系统调用开销).如果无法获取信号量,它会阻塞,将CPU时间放到准备运行的其他线程上.这当然意味着在再次调度线程之前经过几毫秒,但如果这没有问题(通常不是),那么它可以是一种非常有效的,CPU保守的方法.

3.他们如何在拥挤的情况下表现
一种常见的误解是自旋锁或无锁算法"通常更快",或者它们仅对"非常短的任务"有用(理想情况下,不应该保持同步对象更长时间)绝对必要,永远).
一个重要的区别是不同的方法在拥挤的情况下如何表现.

设计良好的系统通常具有较低或没有拥塞(这意味着并非所有线程都试图在同一时间获取锁定).例如,通常不会编写获取锁的代码,然后从网络加载半兆字节的压缩数据,解码并解析数据,最后修改共享引用(将数据附加到容器等)在释放锁之前.相反,只能为了访问共享资源而获取锁.
因为这意味着临界区之外的工作量远远超过其内部,所以线程在临界区内的可能性自然相对较低,因此很少有线程同时争夺锁.当然,每当有两个线程会同时尝试获取锁(如果这不会发生,你就不需要锁!),但这是一个例外而不是"健康"系统中的规则.

在这种情况下,自旋锁的性能大大优于信号量,因为如果没有锁定拥塞,获取自旋锁的开销仅为十几个周期,相比上下文切换的数百/数千个周期或者丢失10-20万个周期时间片的剩余部分.

另一方面,由于拥塞很高,或者锁被长时间保持(有时候你无法帮助它!),一个自旋锁会烧掉疯狂的CPU周期而无法实现任何效果.
在这种情况下,信号量(或互斥)是一个更好的选择,因为它允许不同的线程在此期间运行有用的任务.或者,如果没有其他线程可以做有用的事情,它允许操作系统降低CPU并减少热量/节省能源.

此外,在单核系统上,在锁拥塞的情况下,自旋锁效率会非常低,因为旋转线程将浪费其完整时间等待状态更改,这种状态更改不可能发生(直到发布线程被调度,这不是"T发生等待线程运行时!).因此,在给定任何争用量的情况下,获取锁定在最佳情况下需要大约1 1/2个时间片(假设释放线程是下一个被调度的),这不是非常好的行为.

4.如何实现它们现在,
信号量通常会sys_futex在Linux下进行包装(可选择使用在几次尝试后退出的自旋锁).
自旋锁通常使用原子操作实现,而不使用操作系统提供的任何东西.过去,这意味着使用编译器内在函数或非便携式汇编程序指令.同时C++ 11和C11都将原子操作作为语言的一部分,因此除了编写可证明正确的无锁代码的一般困难之外,现在可以在完全可移植的(几乎)实现无锁代码.无痛的方式.



2> gbjbaanb..:

很简单,信号量是一个"让步"的同步对象,一个自旋锁是一个'busywait'对象.(信号量还有一点,因为它们同步多个线程,不像互斥锁或保护或监视器或保护代码区域与单个线程的关键部分)

在更多情况下你会使用信号量,但是在你要锁定很短时间的时候使用自旋锁 - 锁定成本特别是如果锁定了很多.在这种情况下,在等待受保护资源解锁时稍微旋转锁定会更有效.如果你旋转太久,显然会有性能受到打击.

通常如果你旋转的时间长于线程量子,那么你应该使用信号量.



3> Jonathan Lef..:

除了Yoav Aviram和gbjbaanb所说的,另一个关键点是你永远不会在单CPU机器上使用自旋锁,而信号量在这样的机器上是有意义的.如今,您经常很难找到没有多核,超线程或等效的机器,但在只有一个CPU的情况下,您应该使用信号量.(我相信原因很明显.如果单个CPU忙于等待其他东西释放自旋锁,但是它在唯一的CPU上运行,则锁定不可能被释放,直到当前进程或线程被抢占为止O/S,可能需要一段时间,在抢占发生之前没有任何用处.)


我想说明在单线程系统上使用自旋锁是多么重要.它们是优先反转问题的基础.相信我:你不想调试这些错误.
无论你是否拥有一个或多个CPU,自旋锁都会在Linux内核中全部结束.你到底是什么意思?
@Amigable:我也错了,但我认为我接近于螺旋锁的经典定义.通过抢占式调度,进程可能会在锁定时间旋转直到其时间片结束,或直到中断导致它产生,但如果另一个进程必须提供允许自旋锁锁定的条件,则自旋锁不是在单CPU机器上的好主意.我工作的系统具有自旋锁,并且在进入非忙等待模式之前具有可配置的自旋数上限.这是一个用户级的自旋锁; 内核可能存在差异.

4> 小智..:

来自Rubinni的Linux设备驱动程序

与信号量不同,自旋锁可以用在无法休眠的代码中,例如中断处理程序



5> 小智..:

我不是内核专家,但这里有几点:

如果在编译内核时启用内核抢占,甚至单处理器机器也可以使用自旋锁.如果禁用内核抢占,则自旋锁(可能)扩展为void语句.

此外,当我们尝试比较Semaphore与Spin-lock时,我相信信号量指的是内核中使用的信号量 - 而不是用于IPC(userland)的信号量.

基本上,如果关键部分很小(小于睡眠/唤醒的开销),并且关键部分不会调用任何可以睡眠的东西,则应使用自旋锁!如果临界区较大且可以睡觉,则应使用信号量.

拉曼Chalotra.



6> yoav.aviram..:

Spinlock指的是使用依赖于机器的汇编指令(例如测试和设置)的线程间锁定的实现.它被称为自旋锁,因为线程只是在循环中等待("旋转")反复检查,直到锁变为可用(忙等待).自旋锁用作互斥体的替代品,互斥锁是由操作系统(而不是CPU)提供的设施,因为如果锁定在短时间内,自旋锁的性能会更好.

Semaphor是由IPC操作系统提供的工具,因此它的主要目的是进程间通信.作为操作系统提供的设施,它的性能将不如用于内部锁定的自旋锁的性能(尽管可能).信号量更适合锁定较长时间.

也就是说 - 在汇编中实现splinlocks是棘手的,而且不便携.


所有多线程CPU都需要一个自旋锁指令("测试和设置"),并且它总是作为硬件中的单个指令实现,因为否则总会有一个竞争条件,其中多个线程认为它"拥有"受保护资源.
信号量用于进程间同步,而不是通信.

7> Johan Bezem..:

我想添加我的观察,更一般,而不是特定于Linux.

根据内存架构和处理器功能,您可能需要自旋锁以在多核或多处理器系统上实现信号量,因为在这样的系统中,当两个或多个线程/进程需要时可能会出现竞争情况获得信号量.

是的,如果您的内存架构通过一个内核/处理器锁定内存部分延迟所有其他访问,并且如果您的处理器提供测试和设置,您可以实现没有自旋锁的信号量(但非常小心! ).

但是,由于设计了简单/廉价的多核系统(我在嵌入式系统中工作),并非所有内存架构都支持这样的多核/多处理器功能,只有测试和设置或等效.然后实现可以如下:

获得自旋锁(忙等待)

试图获得信号量

释放自旋锁

如果未成功获取信号量,则暂停当前线程直到信号量被释放; 否则继续关键部分

释放信号量需要按如下方式实现:

获得自旋锁

释放信号量

释放自旋锁

是的,对于OS级别的简单二进制信号量,可以仅使用自旋锁作为替换.但只有要保护的代码段真的非常小.

如前所述,如果您实施自己的操作系统,请务必小心.调试这样的错误很有趣(我的观点,不是很多人共享的),但大多是非常乏味和困难的.

推荐阅读
刘美娥94662
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有