自旋锁与信号量
- 2024-10-31 08:38:00
- admin 原创
- 67
问题描述:
信号量和自旋锁之间的基本区别是什么?
我们什么时候应该使用信号量而不是自旋锁?
解决方案 1:
自旋锁和信号量主要有四个不同之处:
1. 它们是什么自旋锁
是锁的一种可能实现,即通过忙等待(“自旋”)实现的锁。信号量是锁的泛化(或者,反过来,锁是信号量的特殊情况)。通常,但不一定,自旋锁仅在一个进程内有效,而信号量也可用于在不同进程之间进行同步。
锁用于互斥,即每次只有一个线程可以获取锁并继续执行“关键部分”代码。通常,这意味着修改由多个线程共享的某些数据的代码。信号量
有
一个计数器,并允许一个或多个线程获取自身,具体取决于您向其发送的值,以及(在某些实现中)取决于其最大允许值。
到目前为止,我们可以将锁视为信号量的一个特例,其最大值为 1。
2. 它们的作用
如上所述,自旋锁是一种锁,因此是一种互斥(严格为 1 对 1)机制。它通过反复查询和/或修改内存位置(通常以原子方式)来工作。这意味着获取自旋锁是一种“繁忙”操作,可能会长时间(可能永远)消耗 CPU 周期,而实际上却“一事无成”。
这种方法的主要动机是上下文切换的开销相当于旋转几百次(甚至几千次),因此如果可以通过消耗几个旋转周期来获取锁,那么总体而言,这可能非常高效。此外,对于实时应用程序来说,阻止并等待调度程序在未来某个遥远的时间返回给它们可能是不可接受的。
相比之下,信号量要么根本不旋转,要么只旋转很短的时间(作为避免系统调用开销的优化)。如果无法获取信号量,它会阻塞,将 CPU 时间让给准备运行的另一个线程。这当然可能意味着您的线程再次被调度之前会经过几毫秒,但如果这没有问题(通常没有问题),那么它可以是一种非常高效、节省 CPU 的方法。
3. 拥塞时它们的行为方式
一个常见的误解是,自旋锁或无锁算法“通常更快”,或者它们只适用于“非常短的任务”(理想情况下,同步对象不应该被持有超过绝对必要的时间)。
一个重要的区别是不同方法在拥塞时的行为方式。
设计良好的系统通常拥塞程度很低甚至没有拥塞(这意味着并非所有线程都尝试同时获取锁)。例如,通常不会编写这样的代码:获取锁,然后从网络加载半兆字节的 zip 压缩数据,解码并解析数据,最后修改共享引用(将数据附加到容器等),然后释放锁。相反,人们只会为了访问共享资源而获取锁。
由于这意味着临界区外部的工作比内部多得多,因此线程进入临界区内部的可能性自然相对较低,因此很少有线程同时争用锁。当然,偶尔会有两个线程尝试同时获取锁(如果这种情况不会发生,您就不需要锁了!),但这在“健康”系统中只是例外,而不是规则。
在这种情况下,自旋锁的性能大大优于信号量,因为如果没有锁拥塞,获取自旋锁的开销仅仅是十几个周期,而上下文切换则需要数百/数千个周期,丢失剩余时间片则需要一千万到两千万个周期。
另一方面,如果拥塞严重,或者长时间持有锁(有时您就是忍不住!),自旋锁会耗费大量的 CPU 周期,却一无所获。在这种情况下,
信号量(或互斥量)是更好的选择,因为它允许其他线程在此期间运行有用的任务。或者,如果没有其他线程可以做有用的事情,它允许操作系统降低 CPU 速度并减少热量/节省能源。
此外,在单核系统上,如果出现锁拥塞,自旋锁的效率会非常低,因为自旋线程会浪费全部时间等待不可能发生的状态更改(直到释放线程被调度,而等待线程运行时不会发生这种情况!)。因此,无论争用程度如何,在最佳情况下获取锁大约需要 1 1/2 个时间片(假设释放线程是下一个被调度的线程),这不是很好的行为。
4. 它们的实现方式
信号量现在通常sys_futex
在 Linux 下包装(可选使用自旋锁,在几次尝试后退出)。
自旋锁通常使用原子操作实现,而不使用操作系统提供的任何东西。过去,这意味着使用编译器内在函数或不可移植的汇编指令。同时,C++11 和 C11 都将原子操作作为语言的一部分,因此除了编写可证明正确的无锁代码的一般困难之外,现在可以以完全可移植且(几乎)无痛的方式实现无锁代码。
解决方案 2:
非常简单,信号量是一个“产生”的同步对象,自旋锁是一个“忙等待”同步对象。(信号量还有一点不同,它们同步多个线程,不像互斥锁、保护、监视器或临界区那样保护代码区域不受单个线程的影响)
在更多情况下,你会使用信号量,但在你锁定时间很短的情况下,请使用自旋锁 - 锁定是有代价的,特别是如果你锁定了很多次。在这种情况下,等待受保护的资源解锁一段时间可能更有效。显然,如果你旋转太久,性能就会受到影响。
通常,如果旋转的时间比线程量长,那么就应该使用信号量。
解决方案 3:
除了 Yoav Aviram 和 gbjbaanb 所说的之外,另一个关键点是,您永远不会在单 CPU 机器上使用自旋锁,而信号量在这样的机器上是有意义的。如今,您经常很难找到一台没有多核、超线程或同等功能的机器,但在只有一个 CPU 的情况下,您应该使用信号量。(我相信原因很明显。如果单个 CPU 正忙于等待其他东西释放自旋锁,但它在唯一的 CPU 上运行,则在当前进程或线程被操作系统抢占之前,锁不太可能被释放,这可能需要一段时间,并且在抢占发生之前不会发生任何有用的事情。)
解决方案 4:
摘自 Rubinni 的《Linux 设备驱动程序》
与信号量不同,自旋锁可用于不能休眠的代码,例如中断处理程序
解决方案 5:
我不是内核专家,但有以下几点:
如果在编译内核时启用了内核抢占,那么即使是单处理器机器也可以使用自旋锁。如果禁用内核抢占,则自旋锁(可能)会扩展为void语句。
另外,当我们尝试比较信号量和自旋锁时,我相信信号量是指内核中使用的信号量,而不是用于 IPC(用户空间)的信号量。
基本上,如果临界区较小(小于睡眠/唤醒的开销)且临界区不调用任何可以睡眠的东西,则应使用自旋锁!如果临界区较大且可以睡眠,则应使用信号量。
拉曼·查洛特拉。
解决方案 6:
自旋锁是指使用与机器相关的汇编指令(如测试和设置)实现线程间锁定。之所以称为自旋锁,是因为线程只是在循环中等待(“旋转”),反复检查直到锁可用(忙等待)。自旋锁用于替代互斥锁,互斥锁是操作系统(而非 CPU)提供的一种功能,因为如果锁定时间较短,自旋锁的性能会更好。
信号量是操作系统为 IPC 提供的一种工具,因此它的主要用途是进程间通信。作为操作系统提供的一种工具,它的性能不如线程间锁定的自旋锁(尽管可能)。信号量更适合长时间锁定。
话虽如此,在汇编中实现 splinlocks 是比较棘手的,而且不可移植。
解决方案 7:
我想补充一些我的观察,这些观察比较笼统,而不是针对 Linux 的。
根据内存架构和处理器功能,您可能需要自旋锁才能在多核或多处理器系统上实现信号量,因为在这样的系统中,当两个或多个线程/进程想要获取信号量时可能会出现竞争条件。
是的,如果您的内存架构提供一个核心/处理器锁定内存部分,从而延迟所有其他访问,并且如果您的处理器提供测试和设置,那么您可以实现没有自旋锁的信号量(但要非常小心!)。
但是,由于设计了简单/廉价的多核系统(我正在嵌入式系统中工作),并非所有内存架构都支持此类多核/多处理器功能,只有测试和设置或等效功能。然后实现可能如下:
获取自旋锁(忙等待)
尝试获取信号量
释放自旋锁
如果未成功获取信号量,则暂停当前线程,直到释放信号量为止;否则继续执行临界区
释放信号量需要按如下方式实现:
获取自旋锁
释放信号量
释放自旋锁
是的,对于操作系统级别的简单二进制信号量,可以只使用自旋锁作为替代。但前提是要保护的代码段确实非常小。
如前所述,如果您实现自己的操作系统,请务必小心。调试此类错误很有趣(我个人认为,很多人不这么认为),但大多数情况下非常繁琐和困难。
解决方案 8:
当且仅当您非常确定预期结果将在线程执行片时间到期之前很快发生时,才会使用自旋锁。
示例:在设备驱动程序模块中,驱动程序在硬件寄存器 R0 中写入“0”,现在需要等待该 R0 寄存器变为 1。H/W 读取 R0 并执行一些工作,然后在 R0 中写入“1”。这通常很快(以微秒为单位)。现在旋转比进入睡眠状态并被 H/W 中断要好得多。当然,在旋转时,需要注意 H/W 故障情况!
用户应用程序绝对没有理由旋转。这毫无意义。您将旋转以等待某个事件发生,而该事件需要由另一个用户级应用程序完成,而这永远无法保证在短时间内发生。所以,我不会在用户模式下旋转。我最好在用户模式下使用 sleep() 或 mutexlock() 或 semaphore lock()。
解决方案 9:
“互斥锁”(或“互斥锁”)是两个或多个异步进程可以用来保留共享资源以供独占使用的信号。第一个获得“互斥锁”所有权的进程也将获得共享资源的所有权。其他进程必须等待第一个进程释放其对“互斥锁”的所有权,然后才能尝试获取它。
内核中最常见的锁定原语是自旋锁。自旋锁是一种非常简单的单持有者锁。如果某个进程尝试获取自旋锁但不可用,则该进程将继续尝试(自旋),直到能够获取锁为止。这种简单性可创建小型且快速的锁。
解决方案 10:
自旋锁和信号量之间有什么区别?作者:Maciej Piechotka:
两者都管理有限的资源。我首先来描述一下二进制信号量(互斥量)和自旋锁之间的区别。
自旋锁执行忙等待 - 即它保持运行循环:
当(try_acquire_resource())时; ... 发布();
它执行非常轻量级的锁定/解锁,但如果锁定线程被其他尝试访问相同资源的线程抢占,则第二个线程将简单地尝试获取资源,直到用尽其 CPU 量。
另一方面,互斥锁的行为更像是:
如果(!try_lock()){ 添加到等待队列(); 等待(); } ... 进程*p = get_next_process_from_waiting_queue(); p->唤醒();
因此,如果线程尝试获取被阻止的资源,它将被暂停,直到资源可用为止。锁定/解锁要繁重得多,但等待是“自由”和“公平”的。
信号量是一种允许多次使用的锁(从初始化中可知)——例如,允许 3 个线程同时持有资源,但不能更多。它用于生产者/消费者问题或一般队列中:
P(资源消耗) 资源 = 资源.pop() ... 资源.推送(资源) V(资源_sem)
信号量、互斥锁和自旋锁之间的区别?
Linux 中的锁定
解决方案 11:
自旋锁只能由一个进程持有,而信号量可以由一个或多个进程持有。自旋锁等待直到进程释放锁,然后获取锁。信号量是休眠锁,即等待并进入休眠状态。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件