自旋锁对信号灯

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

我们什么时候会在旋转锁上使用信号灯?

79875 次浏览

非常简单,信号量是一个“屈服”的同步对象,自旋锁是一个“忙等待”的对象。(不像互斥锁、保护器、监视器或关键部分那样,信号量可以同步多个线程,从而保护代码区域不受单个线程的影响)

在更多情况下可以使用信号量,但是使用自旋锁可以锁定很短的时间——锁定是有代价的,特别是如果锁定的时间很长。在这种情况下,在等待受保护的资源解锁的同时,自旋锁可能更有效率。很明显,如果你旋转的时间太长,性能会受到影响。

通常,如果自旋时间超过线程量程,则应使用信号量。

自旋锁是指使用与计算机相关的汇编指令(如测试并设置)实现线程间锁定。之所以称为自旋锁,是因为线程只是在循环中等待(“自旋”) ,反复检查,直到锁变得可用(繁忙等待)。自旋锁被用作互斥锁的替代品,互斥锁是操作系统(而非 CPU)提供的一种工具,因为如果锁定的时间较短,自旋锁的性能会更好。

信号是由操作系统为 IPC 提供的工具,因此它的主要用途是进程间通信。作为操作系统提供的一种工具,它的性能将不如自旋锁用于头间锁定(尽管可能)。信号量更适合锁定更长的时间段。

也就是说,在装配中实现样条锁是很棘手的,而且是不可移植的。

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

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

如果在编译内核时启用了内核抢占,那么即使是单处理器机器也可以使用自旋锁。如果内核抢占被禁用,那么自旋锁(可能)扩展为 无效语句。

另外,当我们试图比较信号量和自旋锁时,我相信信号量指的是内核中使用的信号量,而不是用于 IPC (用户域)的信号量。

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

拉曼 · 夏洛特拉。

我想补充一下我的观察,更一般而且不是非常特定于 Linux。

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

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

然而,由于设计了简单/廉价的多核系统(我从事的是嵌入式系统) ,并非所有的内存架构都支持这种多核/多处理器特性,只有测试和集合或等价的特性。那么可以采取以下方式实施:

  • 获得旋转锁(忙于等待)
  • 尝试获取信号灯
  • 释放旋转锁
  • 如果未成功获取信号量,则挂起当前线程,直到释放信号量; 否则继续执行关键部分

释放信号灯的工作需要执行如下:

  • 拿到旋转锁
  • 释放信号灯
  • 释放旋转锁

是的,对于操作系统级别的简单二进制信号量,可以只使用自旋锁作为替代。但前提是需要保护的代码段非常小。

如前所述,如果您实现了自己的操作系统,一定要小心。调试这样的错误很有趣(我的观点,很多人不这么认为) ,但大多数情况下非常乏味和困难。

来自 Rubinni 的 Linux 设备驱动程序

与信号量不同,自旋锁可用于不能睡眠的代码, 例如中断处理程序

“互斥锁”(或“互斥锁锁”)是一个信号,表明两个或多个异步进程可以使用该信号来保留共享资源供独占使用。获得“互斥对象”所有权的第一个进程也获得共享资源的所有权。其他进程必须等待第一个进程释放它对“互斥对象”的所有权,然后才可能尝试获得它。

内核中最常见的锁定原语是自旋锁。自旋锁是一种非常简单的单持锁。如果一个进程试图获取一个自旋锁并且它不可用,该进程将继续尝试(旋转) ,直到它可以获取该锁。这种简单性创建了一个小而快的锁。

自旋锁和信号量主要有四点不同:

它们是什么
自旋锁是锁的一种可能的实现,即通过忙等待(“旋转”)实现的锁。信号量是锁的泛化(或者,反过来说,锁是信号量的特殊情况)。通常,但不一定、自旋锁只在一个进程中有效,而信号量也可用于不同进程之间的同步。

锁可以用于互斥锁,也就是 线程一次可以获得锁并继续处理代码的“关键部分”。通常,这意味着修改由多个线程共享的某些数据的代码。
信号灯有一个计数器,它允许 一个或几个线程获取它自己,这取决于您向它发送的值,并且(在某些实现中)取决于它的最大允许值是什么。

到目前为止,我们可以认为锁是信号量的一种特殊情况,其最大值为1。

2. 他们的工作
如上所述,自旋锁是一种锁,因此是一种互斥锁(严格来说是1比1)机制。它通过重复查询和/或修改内存位置来工作,通常是以原子的方式。这意味着获取自旋锁是一个“繁忙”操作,可能会长时间地消耗 CPU 周期(也许是永远!)而实际上却“一无所获”。
这种方法的主要动机是,上下文切换的开销相当于旋转几百次(或者几千次) ,所以如果可以通过旋转几个周期来获得锁,那么总体上可能会更有效率。此外,对于实时应用程序来说,阻塞和等待调度程序在将来某个遥远的时间返回到它们可能是不可接受的。

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

3. 在充血的情况下他们的行为
常见的误解是,自旋锁或无锁算法“通常更快”,或者它们只对“非常短的任务”有用(理想情况下,没有同步对象应该保持超过绝对必要的时间,永远)。
一个重要的区别是不同的方法如何表现 在充血的情况下

一个设计良好的系统通常拥塞率很低或者没有拥塞(这意味着并非所有线程都试图在同一时间获得锁)。例如,通常 没有编写获取锁的代码,然后从网络加载半兆压缩数据,解码和解析数据,最后在释放锁之前修改共享引用(将数据附加到容器等)。相反,只有在访问 共享资源时才会获取锁。
由于这意味着临界区外的工作要比临界区内的工作多得多,因此临界区内线程的可能性相对较低,因此很少有线程同时争用锁。当然,时不时会有两个线程试图同时获取锁(如果发生这种 不能,就不需要锁了!)但在一个“健康”的体系中,这只是一个例外,而非常规。

在这种情况下,自旋锁 非常喜欢的性能优于信号量,因为如果没有锁拥塞,获取自旋锁的开销仅为十几个周期,而上下文切换的开销为数百/数千个周期,或者丢失一个时间片的其余部分的开销为1000-2000万个周期。

另一方面,考虑到高度拥塞,或者如果锁被持有很长时间(有时你只是不能帮助它!)一个自旋锁会消耗大量的 CPU 周期,却什么也做不了。
在这种情况下,信号量(或互斥量)是一个更好的选择,因为它允许不同的线程在此期间运行 很有用任务。或者,如果没有其他线程有什么有用的事情要做,它允许操作系统节流 CPU 和减少热量/节约能源。

此外,在单核系统上,当存在锁拥塞时,自旋锁会非常低效,因为自旋线程会浪费整个时间等待不可能发生的状态改变(直到调度了释放线程,当等待线程运行时,不可能才会发生).因此,考虑到 任何的争用量,获取锁在最佳情况下大约需要11/2个时间片(假设释放线程是下一个被调度的线程) ,这不是很好的行为。

4. 如何实施
现在,信号量通常在 Linux 下包装 sys_futex(可以选择使用一个自旋锁,几次尝试后退出)。
自旋锁通常使用原子操作实现,而不使用操作系统提供的任何内容。在过去,这意味着要么使用编译器内部特性,要么使用不可移植的汇编器指令。与此同时,C + + 11和 C11都将原子操作作为语言的一部分,因此除了编写可证明正确的无锁代码的普遍困难之外,现在可以以一种完全可移植的(几乎)无痛的方式实现无锁代码。

当且仅当您非常确定预期的结果将在线程的执行切片时间到期之前很快发生时,才使用自旋锁。

示例: 在设备驱动程序模块中,驱动程序在硬件寄存器 R0中写入“0”,现在它需要等待该 R0寄存器变为1。H/W 读取 R0并做一些工作,在 R0中写入“1”。这通常很快(以微秒为单位)。现在纺纱比睡觉和被 H/W 打断要好得多当然,在纺纱的时候,H/W 失效的情况需要注意!

用户应用程序绝对没有理由旋转。这说不通啊。您将旋转一些事件发生,并且该事件需要由另一个用户级应用程序来完成,而这个用户级应用程序永远不能保证在快速时间框架内发生。所以,在用户模式下我不会旋转。我最好在用户模式下睡眠()或 mutexlock ()或信号锁()。

来自 自旋锁和信号量有什么区别? by Maciej Piechotka:

两者都管理有限的资源。我将首先描述二进制信号量(互斥锁)和自旋锁之间的区别。

自旋锁 执行一个繁忙的等待——也就是说,它保持运行循环:

while (try_acquire_resource ());
...
release();

它执行非常轻量级的锁定/解锁,但是如果锁定线程被其他尝试访问相同资源的线程抢占,那么第二个线程将简单地尝试获取资源,直到它耗尽 CPU 量子。
另一方面,互斥对象的行为更像:

if (!try_lock()) {
add_to_waiting_queue ();
wait();
}
...
process *p = get_next_process_from_waiting_queue ();
p->wakeUp ();

因此,如果线程将尝试获取被阻塞的资源,它将被挂起,直到它可用。锁定/解锁要重得多,但等待是“自由的”和“公平的”。

Semaphore 是一个允许多次使用(从初始化开始就知道)的锁——例如,允许3个线程同时保存资源,但不允许更多次。例如,它用于生产者/消费者问题或一般用于队列:

P(resources_sem)
resource = resources.pop()
...
resources.push(resources)
V(resources_sem)

信号量、互斥量和自旋锁之间的区别? ?

锁定 Linux

自旋锁只能由一个进程持有,而信号量可以由一个或多个进程持有。 旋转锁等待进程释放一个锁,然后获取一个锁。 信号灯是睡眠锁,即等待和去睡觉。