自旋锁和互斥锁区别

本文详细介绍了POSIX线程库中的自旋锁机制,包括其工作原理、应用场景及API使用方法。对比互斥锁,自旋锁适用于锁持有时间较短的情况,可提高并发性能。

http://blog.youkuaiyun.com/kyokowl/article/details/6294341

POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套常用的API。线程同步(Thread Synchronization)是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads提供的锁机制(lock)来对多个线程之间共 享的临界区(Critical Section)进行保护(另一种常用的同步机制是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提供的与Spin Lock锁操作相关的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 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。


所以,自旋锁一般用用多核的服务器。 


自旋锁(Spin lock)

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



自旋锁互斥锁在多个方面存在区别: ### 实现机制 - **自旋锁**:底层通常基于原子操作(如 CAS)忙等待实现。当锁被其他执行单元持有时,当前线程会不断循环检查锁是否被释放,即“自旋”,在此期间不会放弃 CPU 资源,一直占用 CPU 周期直到锁可用[^1][^2][^3][^4]。 - **互斥锁**:基于内核调度机制等待队列实现。当锁被其他执行单元持有时,当前线程会进入等待队列并进入休眠状态,让出 CPU 资源。当锁被释放后,内核会从等待队列中唤醒一个线程来获取锁[^4]。 ### 使用场景 - **自旋锁**:适用于锁被持有的时间较短、并发程度高且 CPU 资源充足的场景。因为自旋锁不会导致线程休眠唤醒的开销,如果锁的持有时间很短,自旋等待的时间也会很短,这样可以避免线程切换带来的开销。但在单核 CPU 上使用自旋锁,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程),否则一个自旋的线程永远不会放弃 CPU,导致其他线程无法执行[^1][^2][^3]。 - **互斥锁**:适用于锁被持有的时间较长的场景。当锁的持有时间较长时,使用自旋锁会让线程长时间占用 CPU 进行无意义的自旋,造成 CPU 资源的浪费。而互斥锁可以让线程休眠,将 CPU 资源让给其他线程使用,提高系统的整体性能[^4]。 ### 性能表现 - **自旋锁**:在锁竞争不激烈的情况下,自旋锁的性能较高,因为它避免了线程切换的开销。但在高竞争的情况下,多个线程会不断自旋,导致 CPU 空转,效率降低[^4]。 - **互斥锁**:在高竞争的情况下,互斥锁的公平性更好,因为线程会进入等待队列,按照一定的顺序获取锁。但线程的休眠唤醒操作会带来一定的开销,所以在锁竞争不激烈且锁持有时间短的情况下,性能不如自旋锁[^4]。 ### 优先级反转问题 - **自旋锁**:可能会发生优先级反转问题,即低优先级线程持有锁,高优先级线程需要等待。在某些情况下,可以通过禁用抢占或中断来缓解这个问题,但这会带来其他的复杂性潜在风险[^4]。 - **互斥锁**:可以通过优先级继承机制来缓解优先级反转问题。当一个低优先级线程持有锁,而高优先级线程需要该锁时,低优先级线程会临时提升到高优先级线程的优先级,直到释放锁为止[^4]。 ### 代码示例 以下是一个简单的伪代码示例,展示自旋锁互斥锁的基本使用: ```c // 自旋锁示例 #include <stdatomic.h> // 定义自旋锁 typedef atomic_flag spinlock_t; // 初始化自旋锁 void spinlock_init(spinlock_t *lock) { atomic_flag_clear(lock); } // 获取自旋锁 void spinlock_lock(spinlock_t *lock) { while (atomic_flag_test_and_set(lock)) { // 自旋等待 } } // 释放自旋锁 void spinlock_unlock(spinlock_t *lock) { atomic_flag_clear(lock); } // 互斥锁示例(在 POSIX 系统中) #include <pthread.h> // 定义互斥锁 pthread_mutex_t mutex; // 初始化互斥锁 pthread_mutex_init(&mutex, NULL); // 获取互斥锁 pthread_mutex_lock(&mutex); // 临界区代码 // ... // 释放互斥锁 pthread_mutex_unlock(&mutex); ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值