Spinlock versus Semaphore

Spinlock versus Semaphore

本文关键字:Semaphore versus Spinlock      更新时间:2023-10-16

信号量与;旋转锁定?

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

Spinlock和信号量的区别主要有四点:

1.它们是
自旋锁是锁的一种可能实现,即通过忙等待("旋转")实现的锁。信号量是锁的泛化(或者,反过来说,锁是信号量的特例)。通常,但不一定,自旋锁只在一个进程内有效,而信号量也可以用于在不同进程之间同步。

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

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

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

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

3.它们在拥塞情况下的行为
人们普遍误解自旋锁或无锁算法"通常更快",或者它们只适用于"非常短的任务"(理想情况下,任何同步对象的保持时间都不应超过绝对必要的时间)
一个重要的区别是不同的方法在出现拥塞时的表现

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

在这种情况下,自旋锁大大优于信号量,因为如果没有锁拥塞,获取自旋锁的开销仅为十几个周期,而上下文切换的开销为数百/数千个周期,或者丢失剩余时间片的开销为10-20亿个周期。

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

此外,在单核系统上,在存在锁拥塞的情况下,旋转锁将非常低效,因为旋转线程将浪费其全部时间等待不可能发生的状态更改(直到调度了释放线程,而在等待线程运行时不会发生!)。因此,给定任何量的c

很简单,信号量是一个"屈服"的同步对象,spinlock是一个‘busywait’对象。

在更多的情况下,你会使用信号量,但在你要锁定很短时间的地方使用旋转锁——锁定是有代价的,尤其是如果你锁定了很多。在这种情况下,在等待受保护的资源解锁时旋转锁定一段时间可能会更有效。显然,如果你旋转的时间太长,就会对性能造成影响。

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

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

来自Rubini 的Linux设备驱动程序

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

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

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

此外,当我们试图比较信号量和旋转锁时,我相信信号量指的是内核中使用的信号量,而不是IPC(userland)中使用的。

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

拉曼Chalotra。

Spinlock指的是使用机器相关的汇编指令(如测试和设置)实现线程间锁定。它被称为spinlock,因为线程只是在循环中等待("spins")反复检查,直到锁可用(繁忙等待)。旋转锁被用作互斥锁的替代品,互斥锁是由操作系统(而不是CPU)提供的一种功能,因为如果短时间锁定,旋转锁的性能会更好。

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

也就是说,在程序集中实现splinlocks很棘手,而且不可移植。

我想添加我的观察结果,这些观察结果更一般,但不是非常特定于Linux。

根据内存体系结构和处理器功能的不同,您可能需要旋转锁才能在多核或多处理器系统上实现信号量,因为在这样的系统中,当两个或多个线程/进程想要获取信号量时,可能会出现争用情况。

是的,如果您的内存体系结构通过一个内核/处理器延迟所有其他访问来锁定内存段,并且如果您的处理器提供测试和设置,那么您可以在没有旋转锁的情况下实现信号量(但要非常小心!)。

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

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

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

  • 获取旋转锁定
  • 释放信号灯
  • 释放旋转锁

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

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

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

内核中最常见的锁定原语是spinlock。旋转锁是一种非常简单的单支架锁。如果进程试图获取一个旋转锁,但它不可用,则该进程将继续尝试(旋转),直到它能够获取该锁为止。这种简单性创建了一个小而快速的锁。

Spinlock是在您非常确定预期结果将在线程的执行切片时间到期之前很快发生的情况下使用的。

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

用户应用程序绝对没有旋转的理由。这没有道理。您将为某个事件的发生而旋转,该事件需要由另一个用户级应用程序完成,而这永远不能保证在短时间内发生。所以,我不会在用户模式下旋转。我最好在用户模式下使用sleep()、mutex锁()或信号量锁()。

旋转锁和信号量之间的区别是什么?作者:Maciej Piechotka:

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

旋转锁执行繁忙等待,即保持循环运行:

while(try_acquire_resources());。。。release()

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

if(!try_lock()){add_to_waiting_queue();wait();}。。。process*p=get_next_process_from_waiting_queue();p->唤醒()

因此,如果线程试图获取被阻止的资源,它将被挂起,直到它可以使用为止。锁定/解锁要繁重得多,但等待是"自由的"answers"公平的"。

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

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

信号量、互斥和;spinlock?

在Linux 中锁定

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

Spinlock与Semaphore

信号量与;旋转锁定?

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

Spinlock和信号量的区别主要有四点:

1.它们是
自旋锁是锁的一种可能实现,即通过忙等待("旋转")实现的锁。信号量是锁的泛化(或者,反过来说,锁是信号量的特例)。通常,但不一定,自旋锁只在一个进程内有效,而信号量也可以用于在不同进程之间同步。

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

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

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

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

3.它们在拥塞情况下的行为
人们普遍误解自旋锁或无锁算法"通常更快",或者它们只适用于"非常短的任务"(理想情况下,任何同步对象的保持时间都不应超过绝对必要的时间)
一个重要的区别是不同的方法在出现拥塞时的表现

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

在这种情况下,自旋锁大大优于信号量,因为如果没有锁拥塞,获取自旋锁的开销仅为十几个周期,而上下文切换的开销为数百/数千个周期,或者丢失剩余时间片的开销为10-20亿个周期。

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

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

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

很简单,信号量是一个"屈服"的同步对象,spinlock是一个‘busywait’对象。

在更多的情况下,你会使用信号量,但在你要锁定很短时间的地方使用旋转锁——锁定是有代价的,尤其是如果你锁定了很多。在这种情况下,在等待受保护的资源解锁时旋转锁定一段时间可能会更有效。显然,如果你旋转的时间太长,就会对性能造成影响。

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

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

来自Rubini 的Linux设备驱动程序

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

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

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

此外,当我们试图比较信号量和旋转锁时,我相信信号量指的是内核中使用的信号量,而不是IPC(userland)中使用的。

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

拉曼Chalotra。

Spinlock指的是使用机器相关的汇编指令(如测试和设置)实现线程间锁定。它被称为spinlock,因为线程只是在循环中等待("spins")反复检查,直到锁可用(繁忙等待)。旋转锁被用作互斥锁的替代品,互斥锁是由操作系统(而不是CPU)提供的一种功能,因为如果短时间锁定,旋转锁的性能会更好。

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

也就是说,在程序集中实现splinlocks很棘手,而且不可移植。

我想添加我的观察结果,这些观察结果更一般,但不是非常特定于Linux。

根据内存体系结构和处理器功能的不同,您可能需要旋转锁才能在多核或多处理器系统上实现信号量,因为在这样的系统中,当两个或多个线程/进程想要获取信号量时,可能会出现争用情况。

是的,如果您的内存体系结构通过一个内核/处理器延迟所有其他访问来锁定内存段,并且如果您的处理器提供测试和设置,那么您可以在没有旋转锁的情况下实现信号量(但要非常小心!)。

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

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

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

  • 获取旋转锁定
  • 释放信号灯
  • 释放旋转锁

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

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

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

内核中最常见的锁定原语是spinlock。旋转锁是一种非常简单的单支架锁。如果进程试图获取一个旋转锁,但它不可用,则该进程将继续尝试(旋转),直到它能够获取该锁为止。这种简单性创建了一个小而快速的锁。

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

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

用户应用程序绝对没有旋转的理由。这没有道理。您将为某个事件的发生而旋转,该事件需要由另一个用户级应用程序完成,而这永远不能保证在短时间内发生。所以,我不会在用户模式下旋转。我最好在用户模式下使用sleep()、mutex锁()或信号量锁()。

旋转锁和信号量之间的区别是什么?作者:Maciej Piechotka:

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

旋转锁执行繁忙等待,即保持循环运行:

while(try_acquire_resources());。。。release()

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

if(!try_lock()){add_to_waiting_queue();wait();}。。。process*p=get_next_process_from_waiting_queue();p->唤醒()

因此,如果线程试图获取被阻止的资源,它将被挂起,直到它可以使用为止。锁定/解锁要繁重得多,但等待是"自由的"answers"公平的"。

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

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

信号量、互斥和;spinlock?

在Linux 中锁定

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