温习小知识--mutex和spin lock的区别和应用

来源:互联网 发布:编程求出玫瑰花数 编辑:程序博客网 时间:2024/06/01 01:34

mutex和spin lock的区别和应用(sleep-waiting和busy-waiting的区别)

信号量mutex是sleep-waiting。 就是说当没有获得mutex时,会有上下文切换,将自己、加到忙等待队列中,直到另外一个线程释放mutex并唤醒它,而这时CPU是空闲的,可以调度别的任务处理。

而自旋锁spin lock是busy-waiting。就是说当没有可用的锁时,就一直忙等待并不停的进行锁请求,直到得到这个锁为止。这个过程中cpu始终处于忙状态,不能做别的任务。

例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0 和Core1上。用spin-lock,coer0上的线程就会始终占用CPU。
另外一个值得注意的细节是spin lock耗费了更多的usertime。这就是因为两个线程分别运行在两个核上,大部分时间只有一个线程能拿到锁,所以另一个线程就一直在它运行的core上进行忙等待,CPU占用率一直是100%;而mutex则不同,当对锁的请求失败后上下文切换就会发生,这样就能空出一个核来进行别的运算任务了。(其实这种上下文切换对已经拿着锁的那个线程性能也是有影响的,因为当该线程释放该锁时它需要通知操作系统去唤醒那些被阻塞的线程,这也是额外的开销)

总结
(1)Mutex适合对锁操作非常频繁的场景,并且具有更好的适应性。尽管相比spin lock它会花费更多的开销(主要是上下文切换),但是它能适合实际开发中复杂的应用场景,在保证一定性能的前提下提供更大的灵活度。

(2)spin lock的lock/unlock性能更好(花费更少的cpu指令),但是它只适应用于临界区运行时间很短的场景。而在实际软件开发中,除非程序员对自己的程序的锁操作行为非常的了解,否则使用spin lock不是一个好主意(通常一个多线程程序中对锁的操作有数以万次,如果失败的锁操作(contended lock requests)过多的话就会浪费很多的时间进行空等待)。

(3)更保险的方法或许是先(保守的)使用 Mutex,然后如果对性能还有进一步的需求,可以尝试使用spin lock进行调优。毕竟我们的程序不像Linux kernel那样对性能需求那么高(Linux Kernel最常用的锁操作是spin lock和rw lock)。

 

 

 

 

 

 另,引用如下文章,可以深入学习。


为什么需要内核锁?

多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是可以访问所有内核数据的,因此要对共享数据进行保护,即互斥处理

 

有哪些内核锁机制?

(1)原子操作

atomic_t数据类型,atomic_inc(atomic_t *v)v1

原子操作比普通操作效率要低,因此必要时才使用,且不能与普通操作混合使用

如果是单核处理器,则原子操作与普通操作相同

(2)自旋锁

spinlock_t数据类型,spin_lock(&lock)spin_unlock(&lock)是加锁和解锁

等待解锁的进程将反复检查锁是否释放,而不会进入睡眠状态(忙等待),所以常用于短期保护某段代码

同时,持有自旋锁的进程也不允许睡眠,不然会造成死锁——因为睡眠可能造成持有锁的进程被重新调度,而再次申请自己已持有的锁

如果是单核处理器,则自旋锁定义为空操作,因为简单的关闭中断即可实现互斥

(3)信号量与互斥量

struct semaphore数据类型,down(struct semaphore * sem)up(struct semaphore * sem)是占用和释放

struct mutex数据类型,mutex_lock(struct mutex *lock)mutex_unlock(struct mutex *lock)是加锁和解锁

竞争信号量与互斥量时需要进行进程睡眠和唤醒,代价较高,所以不适于短期代码保护,适用于保护较长的临界区

互斥量与信号量的区别?(转载但找不到原文出处)

(1)互斥量用于线程的互斥,信号线用于线程的同步

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

(2)互斥量值只能为0/1,信号量值可以为非负整数

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问

(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到

 

 

 

 

POSIXthreads(简称Pthreads)是在多核平台上进行并行编程的一套常用的API。线程同步(ThreadSynchronization)是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads提供的锁机制(lock)来对多个线程之间共享的临界区(CriticalSection)进行保护(另一种常用的同步机制是barrier)

Pthreads
提供了多种锁机制:
(1) Mutex
(互斥量):pthread_mutex_***
(2) Spin lock
(自旋锁):pthread_spin_***
(3) Condition Variable
(条件变量):pthread_con_***
(4) Read/Write lock
(读写锁):pthread_rwlock_***

Pthreads
提供的Mutex锁操作相关的API主要有:
pthread_mutex_lock (pthread_mutex_t *mutex);
pthread_mutex_trylock (pthread_mutex_t *mutex);
pthread_mutex_unlock (pthread_mutex_t *mutex);

Pthreads
提供的与SpinLock锁操作相关的API主要有:
pthread_spin_lock (pthread_spinlock_t *lock);
pthread_spin_trylock (pthread_spinlock_t *lock);
pthread_spin_unlock (pthread_spinlock_t *lock);

从实现原理上来讲,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞(blocking)Core0会在此时进行上下文切换(ContextSwitch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spinlock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

 

自旋锁(Spinlock)

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
    1
、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。
    2
、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如copy_to_user()copy_from_user()kmalloc()等。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。
       
自旋锁的用法如下:
  
首先定义:spinlock_tx;
 
然后初始化:spin_lock_init(spinlock_t*x);   //自旋锁在真正使用前必须先初始化
 
2.6.11内核中将定义和初始化合并为一个宏:DEFINE_SPINLOCK(x)
    
 
获得自旋锁:spin_lock(x);  //只有在获得锁的情况下才返回,否则一直自旋
                          spin_trylock(x);  //
如立即获得锁则返回真,否则立即返回假
     
释放锁:spin_unlock(x);
    
结合以上有以下代码段:

    spinlock_t lock;       //
定义一个自旋锁
    spin_lock_init(&lock);
    spin_lock(&lock);    
    .......        //
临界区
    spin_unlock(&lock);   //
释放锁
    
   
还有一些其他用法:
spin_is_locked(x)
    //
  该宏用于判断自旋锁x是否已经被某执行单元保持(即被锁),如果是,  返回真,否则返回假。
spin_unlock_wait(x)
    //
  该宏用于等待自旋锁x变得没有被任何执行单元保持,如果没有任何执行单元保持该自旋锁,该宏立即返回,否
    //
将循环   在那里,直到该自旋锁被保持者释放。

spin_lock_irqsave(lock, flags)
    //
  该宏获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中//断。相当于:spin_lock()+local_irq_save()
spin_unlock_irqrestore(lock, flags)
    //
  该宏释放自旋锁lock的同时,也恢复标志寄存器的值为变量flags保存的//值。它与spin_lock_irqsave配对使用。
    //
相当于:spin_unlock()+local_irq_restore()

spin_lock_irq(lock)
   //该宏类似于spin_lock_irqsave,只是该宏不保存标志寄存器的值。相当        //于:spin_lock()+local_irq_disable()
spin_unlock_irq(lock)
    //
该宏释放自旋锁lock的同时,也使能本地中断。它与spin_lock_irq配对应用。相当于: spin_unlock()+local_irq+enable()

spin_lock_bh(lock)
    //
  该宏在得到自旋锁的同时失效本地软中断。相当于:  //spin_lock()+local_bh_disable()
spin_unlock_bh(lock)
      //
该宏释放自旋锁lock的同时,也使能本地的软中断。它与spin_lock_bh配对//使用。相当于:spin_unlock()+local_bh_enable()

spin_trylock_irqsave(lock, flags)
    //
该宏如果获得自旋锁lock,它也将保存标志寄存器的值到变量flags中,并且失//效本地中断,如果没有获得锁,它什么也不做。因此如果能够立即获得锁,它等//同于spin_lock_irqsave,如果不能获得锁,它等同于spin_trylock。如果该宏//获得自旋锁lock,那需要使用spin_unlock_irqrestore来释放。

spin_trylock_irq(lock)
    //
该宏类似于spin_trylock_irqsave,只是该宏不保存标志寄存器。如果该宏获得自旋锁lock,需要使用spin_unlock_irq来释放。
spin_trylock_bh(lock)
    //
  该宏如果获得了自旋锁,它也将失效本地软中断。如果得不到锁,它什么//也不做。因此,如果得到了锁,它等同于spin_lock_bh,如果得不到锁,它等同//spin_trylock。如果该宏得到了自旋锁,需要使用spin_unlock_bh来释放。
spin_can_lock(lock)
    //
  该宏用于判断自旋锁lock是否能够被锁,它实际是spin_is_locked取反。//如果lock没有被锁,它返回真,否则,返回假。该宏在2.6.11中第一次被定义,在//先前的内核中并没有该宏。
自旋锁的获得和释放有多种方法,要根据场合选择,可以去http://soft.yesky.com/os/lin/10/2303010_4.shtml
看看,介绍了很多关于自旋锁的使用知识。本文引用了很多上面的知识。

0 0
原创粉丝点击