信号量和自旋锁有哪些基本区别?
在什么情况下会选择使用信号量而不是自旋锁?
自旋锁和信号量主要有四个不同之处:
1. 它们的定义
自旋锁是锁的一种实现方式,它是通过忙等待(“自旋”)来实现的。 信号量是锁的一般化(或者反过来说,锁是信号量的一种特殊情况)。通常,自旋锁仅在一个进程内有效,而信号量也可以用于不同进程之间的同步。但不是必须如此。
锁的作用在于互斥,即一次只有一个线程可以获取锁并继续执行“临界区”代码。通常,这意味着修改一些由多个线程共享的数据的代码。
信号量有一个计数器,并允许一个或多个线程获取它,这取决于您发布给它的值以及(在某些实现中)其最大允许值。
因此,可以将锁视为最大值为1的信号量的特例。
2. 它们的作用
如上所述,自旋锁是一种锁,因此是一种互斥(严格1对1)机制。它通过重复查询和/或修改内存位置来工作,通常以原子方式。这意味着获取自旋锁是一种“繁忙”的操作,可能会消耗大量CPU周期的时间(甚至永远!),而实际上没有实现任何有用的操作。
采用这种方法的主要动机在于,上下文切换具有等价于自旋几百次(或者可能是几千次)的开销,因此如果可以通过自旋烧掉一些周期来获取锁,则可能总体上更为高效。对于实时应用程序,阻塞并等待调度程序在未来某个时间回到它们可能是不可接受的。
另一方面,如果出现高度拥塞的情况,或者锁被长时间持有(有时候你就是无法避免!),自旋锁会烧掉大量的CPU周期却毫无作用。
在这种情况下,信号量(或互斥锁)是一个更好的选择,因为它允许另一个线程在此期间运行有用的任务。或者,如果没有其他线程有什么有用的事情要做,它允许操作系统减弱CPU并降低热量/节约能源。
而且,在单核系统上,当存在锁拥塞时,自旋锁会相当低效,因为自旋线程将浪费其所有时间等待一个状态改变,而这个状态改变不可能发生(直到释放线程被调度,但在等待线程运行时,这种情况 不会发生!)。因此,即使存在任何争用,获取锁也需要大约1个半时间片(假设释放线程是下一个被调度的线程),这并不是很好的行为。
4. 它们如何实现
在Linux下,信号量现在通常使用sys_futex
进行包装(可选择使用自旋锁,在几次尝试后退出)。
自旋锁通常使用原子操作实现,而不使用操作系统提供的任何内容。过去,这意味着使用编译器内置函数或非可移植的汇编指令。与此同时,C++11和C11都将原子操作作为语言的一部分,因此除了编写可证明正确的无锁代码的一般难度外,现在完全可以以一种完全可移植且(几乎)不痛苦的方式实现无锁代码。
除了Yoav Aviram和gbjbaanb所说的内容之外,以前的另一个关键点是在单CPU机器上永远不会使用自旋锁(spin-lock),而在这种情况下使用信号量(semaphore)是有意义的。现在,很难找到没有多个核心、超线程或等效功能的机器,但在只有一个CPU的情况下,应该使用信号量。(我相信原因是显而易见的。如果单个CPU正忙于等待其他东西释放自旋锁,但它正在唯一的CPU上运行,则在当前进程或线程被操作系统(preemption)抢占之前,锁不太可能被释放,这可能需要一段时间,并且在抢占发生之前不会发生任何有用的事情。)
来自Rubinni的《Linux设备驱动程序》
与信号量不同,自旋锁可用于不能睡眠的代码中,例如中断处理程序。
我不是内核专家,但以下是一些要点:
即使是单处理器机器,在编译内核时启用内核抢占也可以使用自旋锁。如果禁用内核抢占,则自旋锁(可能)扩展为void语句。
此外,当我们尝试比较信号量和自旋锁时,我认为信号量是指内核中使用的信号量,而不是用于IPC(用户空间)的信号量。
基本上,如果关键部分很小(小于睡眠/唤醒的开销),且关键部分不调用可能会导致休眠的任何内容,则应使用自旋锁。如果关键部分更大且可能会休眠,则应使用信号量。
Raman Chalotra。
自旋锁只能被一个进程持有,而信号量可以被一个或多个进程持有。 自旋锁会等待进程释放锁,然后再获取锁。 信号量是睡眠锁,即等待并进入睡眠状态。
Spinlock只有在您非常确定您的预期结果很快就会发生,即在您的线程执行时间片到期之前使用。
例如:在设备驱动程序模块中,驱动程序在硬件寄存器R0中写入“0”,现在需要等待该R0寄存器变为1。硬件读取R0并进行一些工作,然后在R0中写入“1”。这通常很快(在微秒内)。现在旋转比睡眠好得多,并且不会被硬件中断。当然,在旋转时,需要注意硬件故障条件!
用户应用程序绝对没有旋转的理由。这没有意义。您将旋转以等待某个事件发生,而该事件需要由另一个用户级应用程序完成,这永远不能保证在短时间内发生。因此,在用户模式下我不会旋转。我更喜欢在用户模式下使用sleep()、mutexlock()或semaphore lock()。
来自Maciej Piechotka的自旋锁和信号量之间有什么区别?:
两者都在管理有限资源。我首先会描述二元信号量(互斥锁)和自旋锁之间的区别。while (try_acquire_resource ());它执行非常轻量级的锁定/解锁,但如果锁定线程被其他线程抢占并试图访问相同的资源,则第二个线程将简单地尝试获取资源,直到它耗尽CPU时间片。
if (!try_lock()) { add_to_waiting_queue (); wait(); } process *p = get_next_process_from_waiting_queue (); p->wakeUp ();因此,如果线程尝试获取被阻塞的资源,它将被挂起,直到它可用。 锁定/解锁更加繁重,但等待是“免费”和“公平”的。
P(resources_sem) resource = resources.pop() ... resources.push(resources) V(resources_sem)
spin_trylock
,如果无法获取锁,则立即返回错误代码。自旋锁并不总是那么严格。但是,使用spin_trylock
需要应用程序以正确的方式进行设计(可能是挂起操作的队列,并在此处选择下一个操作,将实际操作留在队列中)。 - Hibou57