127

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

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

4

11 回答 11

148

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

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

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

到目前为止,可以将锁视为最大值为 1 的信号量的特殊情况。

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

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

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

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

在这种情况下,自旋锁的性能大大优于信号量,因为如果没有锁拥塞,获取自旋锁的开销只有几十个周期,而上下文切换需要数百/数千个周期,而丢失则需要 10-2000 万个周期。时间片的剩余部分。

另一方面,考虑到高度拥塞,或者如果锁被持有很长时间(有时你就是忍不住!),自旋锁将消耗大量的 CPU 周期而一无所获。
在这种情况下,信号量(或互斥量)是更好的选择,因为它允许不同的线程在此期间运行有用的任务。或者,如果没有其他线程可以做有用的事情,它允许操作系统降低 CPU 并减少热量/节约能源。

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

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

于 2013-06-20T18:54:22.177 回答
77

很简单,信号量是“产生”同步对象,自旋锁是“忙等待”对象。(信号量还有一点点需要同步多个线程,这与保护代码区域免受单个线程影响的互斥锁、守卫、监视器或临界区不同)

您会在更多情况下使用信号量,但在您将要锁定很短时间的地方使用自旋锁 - 锁定是有成本的,特别是如果您锁定很多。在这种情况下,在等待受保护资源被解锁的同时进行一段时间的自旋锁定会更有效。显然,如果旋转时间过长,性能会受到影响。

通常,如果您旋转的时间超过线程量子,那么您应该使用信号量。

于 2008-10-12T19:06:02.727 回答
28

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

于 2008-10-12T19:53:01.663 回答
19

来自 Rubinni 的 Linux 设备驱动程序

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

于 2011-11-17T03:15:38.567 回答
8

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

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

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

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

拉曼查洛特拉。

于 2010-06-21T18:47:29.507 回答
7

自旋锁是指使用机器相关的汇编指令(例如测试和设置)实现线程间锁定。它被称为自旋锁,因为线程只是在循环中等待(“自旋”)重复检查直到锁可用(忙等待)。自旋锁被用作互斥锁的替代品,互斥锁是操作系统(不是 CPU)提供的一种工具,因为自旋锁在短时间内锁定时性能更好。

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

也就是说 - 在组装中实现 splinlocks 很棘手,而且不便携。

于 2008-10-12T19:18:32.450 回答
6

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

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

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

然而,由于设计了简单/便宜的多核系统(我在嵌入式系统中工作),并非所有内存架构都支持这种多核/多处理器功能,只有测试和设置或等效。那么一个实现可能如下:

  • 获取自旋锁(忙于等待)
  • 尝试获取信号量
  • 释放自旋锁
  • 如果没有成功获取信号量,则暂停当前线程,直到信号量被释放;否则继续关键部分

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

  • 获得自旋锁
  • 释放信号量
  • 释放自旋锁

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

如前所述,如果您实现自己的操作系统,请务必小心。调试此类错误很有趣(我认为,很多人不同意),但大多非常乏味和困难。

于 2011-11-15T10:02:05.997 回答
1

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

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

于 2012-05-21T07:18:03.193 回答
1

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

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

用户应用程序绝对没有理由旋转。这没有意义。您将旋转以使某些事件发生,并且该事件需要由另一个用户级应用程序完成,而该应用程序永远不能保证在快速时间范围内发生。所以,我根本不会在用户模式下旋转。我最好在用户模式下 sleep() 或 mutexlock() 或 semaphore lock()。

于 2013-06-21T05:59:03.810 回答
1

自旋锁和信号量有什么区别?通过Maciej Piechotka

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

自旋锁执行繁忙的等待 - 即它保持循环运行:

而(try_acquire_resource());
 ...  
释放();

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

如果(!try_lock()){
    add_to_waiting_queue ();
    等待();
}
...
进程 *p = get_next_process_from_waiting_queue ();
p->唤醒();

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

信号量是一个允许多次使用(从初始化中知道)次数的锁——例如,允许 3 个线程同时持有资源,但不能更多。例如,它用于生产者/消费者问题或一般用于队列:

P(resources_sem)
资源 = 资源.pop()
...
resources.push(资源)
V(resources_sem)

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

在 Linux 中锁定

于 2013-06-24T11:20:56.593 回答
1

自旋锁只能由一个进程持有,而信号量可以由一个或多个进程持有。自旋锁等待,直到进程释放锁,然后获取锁。信号量是睡眠锁,即等待并进入睡眠状态。

于 2019-05-15T16:31:44.290 回答