信号量和自旋锁区别

来源:互联网 发布:网络与新媒体概论 pdf 编辑:程序博客网 时间:2024/05/01 04:33

信号量和自旋锁区别

自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。

------------------------------------------------------
虽然听起来两者之间的使用条件复杂,其实在实际使用中信号量和自旋锁并不易混淆。注意以下原则:
如果代码需要睡眠——这往往是发生在和用户空间同步时——使用信号量是唯一的选择。由于不受睡眠的限制,使用信号量通常来说更加简单一些。如果需要在自旋 锁和信号量中作选择,应该取决于锁被持有的时间长短。理想情况是所有的锁都应该尽可能短的被持有,但是如果锁的持有时间较长的话,使用信号量是更好的选 择。另外,信号量不同于自旋锁,它不会关闭内核抢占,所以持有信号量的代码可以被抢占。这意味者信号量不会对影响调度反应时间带来负面影响。


自旋锁对信号量
------------------------------------------------------
需求                     建议的加锁方法

低开销加锁                     优先使用自旋锁
短期锁定                         优先使用自旋锁
中断上下文中加锁          使用自旋锁

长期加锁                               优先使用信号量
持有锁是需要睡眠、调度     使用信号量
————————————————————————————————
自旋锁
------------------------------------------------------
    自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁)。
   自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的内核任务便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。
    自旋锁的基本形式如下:
                 spin_lock(&mr_lock);
                 
//临界区
                 spin_unlock(&mr_lock);

       因为自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区中。这点很好地满足了对称多处理机器需要的锁定服务。在单处 理器上,自旋锁仅仅当作一个设置内核抢占的开关。如果内核抢占也不存在,那么自旋锁会在编译时被完全剔除出内核。
       简单的说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。另外自旋锁不允许任务睡眠 持有自旋锁的任务睡眠会造成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再次申请自己已持有的锁),它能够在中断上下文中使用。
       死锁:假设有一个或多个内核任务和一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。这便会发生所有内核任务都在相互等待, 但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便意味着死锁发生了。自死琐是说自己占有了某个资源,然后 自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。

一、为什么用自旋锁
    操作系统锁机制的基本原理,就是在某个锁操作过程中不能与其他锁操作交织执行,以免多个执行路径对内核中某些重要的数据及数据结构进行同时操作而造成混乱。在不同的系统环境中,根据系统特点和操作需要,锁机制可以用多种方式来实现。以Linux为例,其系统内核的锁机制一般通过3 种基本方式来实现,即原语、关中断和总线锁

       在单CPU系统中,CPU 的读—修改—写原语可以保证是原子的,即执行过程过中不会被中断,所以CPU 通过关中断的方式,从芯片级保证该操作所存取的数据不能被多个内核控制路径同时访问,避免交叉执行。然而,在对称多处理器 (SMP) 环境中,单CPU 涉及读—修改—写原语不再是原子的,因为,在某个CPU 执行读—修改—写指令时有多次总线操作,其他CPU 竞争总线,可导致对同一存储单元的读—写操作与其他CPU 对这一存储单元交叉这时我们就需要用一个称为自旋锁(spin lock)的原始对象为CPU 提供锁定总线的方法

二、自旋锁是什么
    自旋锁(spin lock)是一个典型的对临界资源的互斥手段,它的名称来源于它的特性。为了获得一个自旋锁,在某CPU上运行的代码需先执行一个原子操作,该操作测试并设置(test-and-set)某个内存变量,由于它是原子操作,所以在该操作完成之前其它CPU不可能访问这个内存变量。如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行。如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置(test-and-set)”操作,即开始“自旋”。最后,锁的所有者通过重置该变量释放这个自旋锁,于是,某个等待的test-and-set操作向其调用者报告锁已释放。

三、关于自旋锁的几个事实
   自旋锁实际上是忙等锁,当锁不可用时,CPU一直循环执行“测试并设置(test-and-set)”该锁直到可用而取得该锁,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。这说明只有在占用锁的时间极短的情况下,使用自旋锁是合理的,因为此时某个CPU可能正在等待这个自旋锁。当临界区较为短小时,如只是为了保证对数据修改的原子性,常用自旋锁;当临界区很大,或有共享设备的时候,需要较长时间占用锁,使用自旋锁就不是一个很好的选择,会降低CPU的效率。
   自旋锁也存在死锁(deadlock)问题。引发这个问题最常见的情况是要求递归使用一个自旋锁,即如果一个已经拥有某个自旋锁的CPU想第二次获得这个自旋锁,则该CPU将死锁。自旋锁没有与其关联的“使用计数器”或“所有者标识”;锁或者被占用或者空闲。如果你在锁被占用时获取它,你将等待到该锁被释放。如果碰巧你的CPU已经拥有了该锁,那么用于释放锁的代码将得不到运行,因为你使CPU永远处于“测试并设置”某个内存变量的自旋状态。另外,如果进程获得自旋锁之后再阻塞,也有可能导致死锁的发生。由于自旋锁造成的死锁,会使整个系统挂起,影响非常大。
  自旋锁一定是由系统内核调用的。不可能在用户程序中由用户请求自旋锁。当一个用户进程拥有自旋锁期间,内核是把代码提升到管态的级别上运行。在内部,内核能获取自旋锁,但任何用户都做不到这一点

四、自旋锁与信号量比较
    自旋锁和信号量是解决互斥问题的基本手段,无论是单处理系统还是多处理系统,它们可以不需修改代码地进行移植。那么,这两个手段应该如何选择呢?这就要考虑临界区的性质和系统处理的要求。
    从严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现有赖于后者。
信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的
如果竞争不上,会有上下文切换,进程可以去睡眠,但CPU不会停,会接着运行其他的执行路径。从概念上说,这与单CPU或多CPU没有直接的关系,只是在信号量本身的实现上,为了保证信号量结构存取的原子性,在多CPU中需要自旋锁来互斥。但是值得注意的是上下文切换需要一定时间,并且会使高速缓冲失效,对系统性能影响是很大的因此,只有当进程占用资源很长时间时,用信号量才是不错的选择。
   当所要保护的临界区比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是CPU得不到自旋锁会在那里空转直到锁成功为止,所以要求锁不能在临界区里停留很长时间,否则会降低系统的效率。

   综上,自旋锁是一种保护数据结构或代码片段的原始方式,主要用于SMP中,用于CPU同步,在某个时刻只允许一个进程访问临界区内的代码。它的实现是基于CPU锁定数据总线的指令。为保证系统效率,自旋锁锁定的临界区一般比较短。在单CPU系统中,使用自旋锁的意义不大,还容易因为递归调用自旋锁造成死锁。



信号量
------------------------------------------------------
Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行 其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。

信号量基本使用形式为:
static DECLARE_MUTEX(mr_sem);//声明互斥信号量
if(down_interruptible(&mr_sem))
//可被中断的睡眠,当信号来到,睡眠的任务被唤醒 
//临界区
up(&mr_sem);

原创粉丝点击