旋转与信号量

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

我们什么时候使用旋转locking的信号?

Spinlock和信号量主要有四点不同:

他们是什么
自旋锁的一种可能的实现,即通过繁忙等待(“旋转”)来实现的锁。 信号量是锁的概括(或者,另一种方式,锁是信号量的特殊情况)。 通常但不是必须的 ,自旋锁只在一个进程中有效,而信号量可以用来在不同的进程之间进行同步。

锁的工作原理是互斥,即一次一个线程可以获得锁并继续执行代码的“关键部分”。 通常,这意味着代码修改了一些由多个线程共享的数据。
一个信号量有一个计数器,并允许自己被一个或几个线程获取,具体取决于你发布什么值,以及(在一些实现中)取决于它的最大允许值。

就此而言,可以考虑一个最大值为1的信号量的特殊情况。

2.他们做什么
如上所述,自旋锁是一个锁,因此是一个互斥(严格为1到1)的机制。 它通过反复查询和/或修改内存位置来工作,通常以primefaces方式。 这意味着获取一个自旋锁是一个“繁忙”的操作,可能会长时间(可能永远!)焚烧CPU周期,而它实际上“无”。
这种方法的主要动机是这样一个事实,即上下文切换的开销相当于旋转几百次(或者甚至上千次),所以如果能够通过燃烧几个循环来获得locking,则总的来说可能是更高效。 另外,对于实时应用程序,在未来的某个时间段阻止并等待调度程序返回给它们可能是不可接受的。

相比之下,信号量根本不旋转,或者只在很短的时间内旋转(作为避免系统调用开销的优化)。 如果无法获取信号量,则会阻塞,并将CPU时间放弃到准备运行的其他线程。 这当然可能意味着再次调度线程之前会经过几毫秒的时间,但如果这没有问题(通常不是这样),那么它可能是一个非常有效的CPU保守方法。

3.他们如何在拥挤的情况下行事
这是一个常见的误解:自旋锁或无锁algorithm“通常更快”,或者它们只对“非常短的任务”有用(理想情况下,没有同步对象应该持续超过绝对必要的时间)。
一个重要的区别是不同的方法在拥塞情况下如何performance。

一个devise良好的系统通常拥有很低或者没有拥塞(这意味着并不是所有的线程都试图在同一时间获得锁)。 例如,通常不会编写获取锁的代码,然后从networking中加载半个zip压缩的数据,解码并parsing数据,最后修改共享引用(将数据附加到容器等)在释放锁之前。 相反,只有为了访问共享资源才会获得locking。
因为这意味着在关键部分以外的工作比在其内部有更多的工作,所以在关键部分内部线程的可能性自然是相对较低的,因此很less线程同时争夺locking。 当然,有时两个线程会同时尝试获取锁(如果这种情况不会发生,你就不需要锁),但这是一个例外,而不是“健康”系统中的规则。

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

另一方面,由于拥挤程度很高,或者如果locking时间过长(有时你无法帮到它!),自旋锁将会燃烧疯狂的CPU周期数量,从而达不到任何效果。
信号量(或互斥量)在这种情况下是一个更好的select,因为它允许不同的线程在那段时间内运行有用的任务。 或者,如果没有其他线程有什么用处,它允许操作系统节streamCPU并降低热量/节约能源。

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

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

很简单,信号量是一个“屈服”的同步对象,螺旋锁是一个“忙碌的等待”对象。 (信号量稍微多一些,因为它们可以同步多个线程,不像互斥锁或监视器或监视器或关键部分可以保护代码区域免受单个线程的影响)

在更多的情况下,你会使用一个信号量,但是使用一个自旋锁,你要locking很短的时间 – locking成本特别是locking很多。 在这种情况下,等待受保护的资源解锁可能会更有效。 如果旋转时间太长,显然会有性能问题。

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

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

从Rubinni的Linux设备驱动程序

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

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

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

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

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

拉曼Chalotra。

Spinlock是指使用机器相关的汇编指令(如testing和设置)实现的线程间locking。 它被称为自旋锁(spinlock),因为线程简单地等待循环(“旋转”)重复检查直到锁可用(忙等待)。 自旋锁被用作互斥体的替代品,互斥体是由操作系统(而不是CPU)提供的工具,因为自旋锁执行得更好,如果locking很短的时间。

信号是由IPC的操作系统提供的设备,因此它的主要目的是进行进程间通信。 作为操作系统提供的设施,它的性能将不如螺旋锁那样好(虽然可能)。 信号量更适合locking更长的时间。

这就是说 – 在汇编中实现splinlocks是棘手的,而不是便携式的。

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

根据内存体系结构和处理器function的不同,可能需要一个自旋锁才能在多核或多处理器系统上实现信号量,因为在这样的系统中,当两个或多个线程/进程想要获得一个信号量。

是的,如果你的内存体系结构通过一个核心/处理器来延迟所有其他访问来提供内存部分的locking,并且如果你的处理器提供了一个testing和设置,那么你可以实现一个没有旋转locking的信号量(但是非常小心! )。

然而,由于devise了简单/廉价的多核系统(我在embedded式系统中工作),并不是所有的内存架构都支持这种多核/多处理器function,只能进行testing和设置。 那么一个实现可能如下:

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

释放信号量需要执行如下:

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

是的,对于操作系统级的简单二元信号量,只能使用自旋锁来代替。 但是只有当代码段被保护的时候真的很小。

如前所述,如果您实施自己的操作系统,请务必小心。 debugging这样的错误是有趣的(我的意见,不是很多人共享),但大多是非常乏味和困难。

“互斥锁”(或“互斥锁”)是两个或多个asynchronous进程可用于保留共享资源以供专用的信号。 获得“互斥体”所有权的第一个过程也获得共享资源的所有权。 其他进程必须等待第一个进程释放它对“互斥量”的所有权,然后才能获得它。

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

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

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

用户应用程序绝对没有理由旋转。 这没有意义。 您将旋转一些事件发生,并且该事件需要由另一个用户级别的应用程序来完成,这个应用程序永远不会在快速的时间范围内发生。 所以,我不会在用户模式下旋转。 我最好在用户模式下sleep()或mutexlock()或信号量锁()。

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

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

旋转锁执行一个繁忙的等待 – 即它保持运行循环:

 while(try_acquire_resource()); 
  ...  
发布(); 

它执行非常轻量级的locking/解锁,但如果locking线程将被其他尝试访问相同资源的其他线程抢占,则第二个将简单地尝试释放资源,直到CPU量子运行完毕。
另一方面互斥体的行为更像是:

 if(!try_lock()){
     add_to_waiting_queue();
    等待();
 }
 ...
进程* p = get_next_process_from_waiting_queue();
 p-> wakeUp();

因此,如果线程将尝试获取阻塞的资源,它将被暂停,直到它可用。 locking/解锁更重,但等待是“自由”和“公平”。

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

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

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

在Linux中locking