信号量和自旋锁之间的基本区别是什么?
什么时候在自旋锁上使用信号灯?
最佳答案
自旋锁和信号灯的主要区别在于四点:
1.它们是
自旋锁是一种可能的锁实现方式,即通过忙等待(“自旋”)实现的一种锁。信号量是锁的概括(或者相反,锁是信号量的特例)。通常,但不是必须,自旋锁仅在一个进程内有效,而信号量也可以用于在不同进程之间进行同步。
锁适用于相互排斥,即一次一次一个线程可以获取该锁并继续执行代码的“关键部分”。通常,这意味着修改一些由多个线程共享的数据的代码。
信号量具有一个计数器,并将允许它由一个或多个线程获取,具体取决于您向其发布的值,以及(在某些实现中)取决于其最大允许值。
就此而言,可以认为锁是信号量的一种特殊情况,最大值为1。
2.他们做什么
如上所述,自旋锁是锁,因此是互斥(严格从1到1)机制。它通常通过原子方式反复查询和/或修改内存位置来工作。这意味着获取自旋锁是一种“繁忙”的操作,它可能会长时间(甚至永远!)消耗CPU周期,而实际上却什么也没实现。
这种方法的主要诱因是这样一个事实,即上下文切换的开销相当于旋转数百次(或数千次),因此,如果可以通过燃烧几个循环来获取锁,那么总体而言,这很可能是更高效。同样,对于实时应用程序,阻塞并等待调度程序在将来某个遥远的时间返回到它们可能是 Not Acceptable 。
相比之下,信号量根本不旋转,或者仅旋转很短的时间(作为避免syscall开销的优化)。如果无法获取信号量,它将阻塞,从而将CPU时间浪费在准备运行的其他线程上。当然,这可能意味着在重新安排线程之前要经过几毫秒,但是如果这没问题(通常不是问题),那么它可能是一种非常有效的,CPU节约的方法。
3.出现拥塞时的行为
常见的误解是自旋锁或无锁算法通常“速度更快”,或者仅对“非常短的任务”有用(理想情况下,任何同步对象的保留时间都不应超过绝对必要的时间)。
一个重要的区别是存在拥塞时不同方法的行为。
设计良好的系统通常拥塞少或没有拥塞(这意味着并非所有线程都试图在同一时间获取锁)。例如,通常不会编写获取锁的代码,然后从网络加载半兆字节的zip压缩数据,对数据进行解码和解析,最后修改共享的引用(将数据附加到容器等)。在释放锁之前。取而代之的是,仅出于访问共享资源的目的而获得锁。
由于这意味着关键部分之外的工作要多于关键部分内部的工作,因此自然而然地,在关键部分之内的线程的可能性相对较低,因此很少有线程同时争用锁。当然,不时有两个线程会尝试同时获取锁(如果不可能的话,您就不需要锁!),但这是一个异常(exception),而不是“健康”系统中的规则。
在这种情况下,自旋锁的性能大大优于信号量,因为如果没有锁拥塞,则获取自旋锁的开销仅为十几个周期,而上下文切换则为数百/数千个周期,而丢失则为10-20百万个周期时间片的其余部分。
另一方面,由于拥塞程度很高,或者如果锁被长时间持有(有时您无能为力!),自旋锁将消耗大量的CPU周期以至于什么也没做。
在这种情况下,信号灯(或互斥锁)是更好的选择,因为它允许不同的线程在这段时间内运行有用的任务。或者,如果没有其他线程有用,它将允许操作系统降低CPU的速度并减少热量/节约能源。
同样,在单核系统上,自旋锁在存在锁拥塞的情况下效率很低,因为旋转线程将浪费其全部时间来等待状态改变(除非计划了释放线程,否则不会发生这种情况)等待线程正在运行时不会发生!)。因此,在有任何争用的情况下,在最佳情况下获取锁大约需要1 1/2个时间片(假设释放线程是正在计划的下一个线程),这不是很好的行为。
4.如何实现
如今,一个信号量通常会在Linux下包装sys_futex
(可选地,带有几次自旋锁,在尝试几次后会退出)。
自旋锁通常使用原子操作来实现,而不使用操作系统提供的任何功能。过去,这意味着使用编译器内部函数或非便携式汇编器指令。同时,C++ 11和C11都将原子操作作为语言的一部分,因此,除了编写可证明正确的无锁代码的一般困难之外,现在有可能在完全可移植且(几乎)可移植的代码中实现无锁代码。无痛的方式。
关于c++ - 自旋锁与信号量,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/195853/