Answers:
自旋锁和信号灯的主要区别在于四点:
1.他们是什么
一个自旋锁是一个可能实现的锁,即一个由忙等待(“旋转”)来实现。信号量是锁的概括(或者相反,锁是信号量的特例)。通常,但不一定,自旋锁仅在一个进程内有效,而信号量也可用于在不同进程之间进行同步。
锁可以互斥,即一次一个线程可以获取该锁并继续执行代码的“关键部分”。通常,这意味着修改一些由多个线程共享的数据的代码。
一个信号有一个计数器,并允许自己被收购一个或多个线程,这取决于你的价值张贴到它的东西,(在一些实现),这取决于它的最大允许值是什么。
就此而言,可以认为锁是信号量的特殊情况,最大值为1。
2.他们的工作
如上所述,自旋锁是一种锁,因此是一种互斥(严格从1到1)的机制。它通常通过原子方式反复查询和/或修改内存位置来工作。这意味着获取自旋锁是“忙碌”的操作,可能长时间(甚至永远!)消耗CPU周期,而实际上却无法实现“空”。
这种方法的主要诱因是这样一个事实,即上下文切换的开销相当于旋转几百(或数千)次,因此,如果可以通过燃烧几个循环来获得锁,那么总体来说可能是更高效。同样,对于实时应用程序,阻塞并等待调度程序在将来某个遥远的时间返回它们可能也是不可接受的。
相比之下,信号量根本不旋转,或者仅旋转很短的时间(作为避免syscall开销的优化)。如果无法获取信号量,它将阻塞,从而将CPU时间浪费在准备运行的其他线程上。当然,这可能意味着在重新安排线程之前要经过几毫秒,但是如果这没问题(通常不是问题),那么它可能是一种非常有效的,CPU节约的方法。
3.它们在出现拥塞时的行为
常见的误解是自旋锁或无锁算法通常“速度更快”,或者仅对“非常短的任务”有用(理想情况下,不应将同步对象保留更长时间)绝对没有必要)。
一个重要的区别是存在拥塞时不同方法的行为方式。
设计良好的系统通常拥塞少或没有拥塞(这意味着并非所有线程都试图在同一时间获取锁)。例如,通常不会编写获取锁的代码,然后从网络加载半兆字节的zip压缩数据,对数据进行解码和解析,最后修改共享引用(将数据追加到容器等)。释放锁之前。取而代之的是,仅出于访问共享资源的目的而获得该锁。。
由于这意味着关键部分之外的工作要多于关键部分内部的工作,因此自然而然地,在关键部分内部的线程的可能性相对较低,因此很少有线程同时争用锁。当然,偶尔会有两个线程尝试同时获取锁(如果不可能的话,您就不需要锁!),但这是一个例外,而不是“健康”系统中的规则。
在这种情况下,自旋锁的性能大大优于信号量,因为如果没有锁拥塞,则获取自旋锁的开销仅为十几个周期,而上下文切换则为数百/数千个周期,而丢失则为10-20百万个周期时间片的其余部分。
另一方面,由于拥塞程度很高,或者如果锁被长时间持有(有时您无能为力!),自旋锁将消耗大量的CPU周期以至于什么也没做。
在这种情况下,信号灯(或互斥锁)是更好的选择,因为它允许不同的线程在这段时间内运行有用的任务。或者,如果没有其他线程有用,则它允许操作系统节流CPU并减少热量/节省能量。
此外,单核系统上,一个自旋锁会在锁拥塞存在相当低效,作为纺丝线会浪费的状态改变是不可能发生的(直到释放线程调度,其中其完整的时间等待ISN等待线程正在运行时不会发生!)。因此,在有任何争用的情况下,在最佳情况下获取锁大约需要1 1/2个时间片(假设释放线程是正在计划的下一个线程),这不是很好的行为。
4.如何实现
如今,信号量通常会sys_futex
在Linux下包装(可选地,自旋锁会在几次尝试后退出)。
自旋锁通常使用原子操作来实现,而不使用操作系统提供的任何操作。过去,这意味着使用编译器内部函数或非便携式汇编程序指令。同时,C ++ 11和C11都将原子操作作为语言的一部分,因此,除了编写可证明正确的无锁代码的一般困难之外,现在有可能在完全可移植且(几乎)可移植的代码中实现无锁代码。无痛的方法。
除了Yoav Aviram和gbjbaanb所说的以外,另一个关键点过去是,您永远不会在单CPU机器上使用自旋锁,而在这种机器上使用信号量是有意义的。如今,您经常很难找到没有多核,超线程或同等功能的机器,但是在只有一个CPU的情况下,应该使用信号量。(我相信原因很明显。如果单个CPU忙于等待其他操作来释放自旋锁,但它正在唯一的CPU上运行,则在当前进程或线程被以下命令抢占之前,该锁不大可能被释放O / S,这可能需要一段时间,并且在抢占之前没有任何用处。)
自旋锁是指使用依赖于机器的汇编指令(例如,测试并设置)实现线程间锁定的实现。之所以称为自旋锁,是因为线程只是简单地在循环中等待(“自旋”),反复检查直到锁变为可用(繁忙等待)。自旋锁可以用作互斥锁的替代品,互斥锁是操作系统(不是CPU)提供的一种功能,因为自旋锁如果锁定时间较短,性能会更好。
信号量是操作系统为IPC提供的功能,因此它的主要目的是进程间通信。作为操作系统提供的一种工具,它的性能将不如用于跨轴锁定的自旋锁的性能好(尽管可能)。信号量更适合长时间锁定。
就是说-在组装中实现夹板是棘手的,而且不便于移植。
我想补充一下我的看法,这些看法比较笼统,但不是特定于Linux。
根据内存体系结构和处理器功能的不同,您可能需要自旋锁才能在多核或多处理器系统上实现信号量,因为在此类系统中,当两个或多个线程/进程需要一个或多个线程/进程时,可能会发生竞争状态获取信号量。
是的,如果您的内存体系结构通过一个内核/处理器延迟了所有其他访问来锁定某个内存部分,并且如果您的处理器提供了测试并设置,则可以实现没有自旋锁的信号灯(但要非常小心! )。
但是,由于设计了简单/廉价的多核系统(我在嵌入式系统中工作),所以并非所有的内存架构都支持这种多核/多处理器功能,仅支持测试设置或等效功能。那么一个实现可以如下:
释放信号量将需要执行以下步骤:
是的,对于OS级别上的简单二进制信号量,可以仅使用自旋锁作为替换。但是,只有要保护的代码段确实很小时,才可以。
如前所述,如果以及当您实现自己的操作系统时,请务必小心。调试这样的错误很有趣(我认为,很多人并不认同),但大多数情况下非常繁琐且困难。
“互斥锁”(或“互斥锁”)是两个或多个异步进程可以用来保留共享资源以供独占使用的信号。获得“互斥体”所有权的第一个过程也获得共享资源的所有权。其他进程必须等待第一个进程释放对“互斥体”的所有权,然后才能尝试获取它。
内核中最常见的锁定原语是自旋锁。自旋锁是一个非常简单的单持有人锁。如果某个进程尝试获取自旋锁,但该锁不可用,则该进程将继续尝试(自旋)直到可以获取该锁。这种简单性创建了一个小型且快速的锁。
当且仅当您非常确定预期结果将在线程的执行切片时间到期之前不久发生时,才使用Spinlock。
示例:在设备驱动程序模块中,驱动程序在硬件寄存器R0中写入“ 0”,现在它需要等待该R0寄存器变为1。H/ W读取R0并执行一些工作,然后在R0中写入“ 1”。这通常很快(以微秒为单位)。现在旋转比睡觉和被硬件打断要好得多。当然,在旋转时,需要注意硬件故障条件!
用户应用程序绝对没有旋转的理由。这没有道理。您将旋转发生某个事件,并且该事件需要由另一个用户级应用程序完成,而该应用程序永远不能保证在快速的时间内发生。因此,我不会在用户模式下旋转。我最好在用户模式下进入sleep()或mutexlock()或信号锁定()。
从什么是自旋锁和信号量之间的区别?由Maciej Piechotka撰写:
两者都管理有限的资源。我将首先描述二进制信号量(互斥体)和自旋锁之间的区别。
自旋锁执行繁忙的等待-即它保持运行循环:
while(try_acquire_resource()); ... 释放();它执行非常轻量级的锁定/解锁,但是如果锁定线程被其他线程抢占(将尝试访问相同的资源),则第二个线程将简单地尝试释放资源,直到耗尽CPU数量。
另一方面,互斥体的行为更像:如果(!try_lock()){ add_to_waiting_queue(); 等待(); } ... 进程* p = get_next_process_from_waiting_queue(); p-> wakeUp();因此,如果线程尝试获取被阻止的资源,它将被挂起,直到对它可用为止。锁定/解锁要沉重得多,但是等待是“自由”和“公平”的。
信号量是一种允许多次使用(从初始化开始就知道)的锁-例如,允许3个线程同时保存资源,但不得多。例如,它用于生产者/消费者问题或一般在队列中使用:
P(资源_sem) 资源= resources.pop() ... resources.push(资源) V(resources_sem)
spin_trylock
,如果无法获取该锁,则会立即返回错误代码。自旋锁并不总是那么苛刻。但是spin_trylock
对于一个应用程序,使用要求以这种方式进行适当的设计(可能是一个待处理操作队列,在这里,选择下一个操作,将实际操作留在队列中)。