一、什么是spinlock
spinlock又称自旋锁,是实现保护共享资源而提出一种锁机制。自旋锁与互斥锁比较类似,都是为了解决对某项资源的互斥使用
无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名
二、spinlock的原理
跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:死锁和过多占用cpu资源
- a. 在用户态尝试竞争一个共享资源. 如果竞争不到, 则不断尝试竞争. 但是不借助内核提供的mutex等变量机制. 因为涉及到内核,就意味这效率低下
- b. 要想在用户态实现竞争一个共享资源, 必须借助cpu提供的原子操作指令. 如果是SMP多cpu,还需要lock指令锁总线
- c. 为了避免在长时间竞争却一直得不到资源导致的不断尝试浪费cpu, 在每两次尝试之间间隔一段时间. 并且随着尝试次数的增加,间隔时间也增加.间隔期间可以让cpu稍加休息(注意,绝不是让出cpu),这依赖于cpu提供pausse指令. (当然如果cpu没有提供pause也没关系,只是会很消耗电力资源)PAUSE指令提升了自旋等待循环(spin-wait loop)的性能
- d. 在等待相当长时间还是得不到锁之后,只好让出cpu. 但必须让出很小一会. 否则就不叫自旋锁了
三、spinlock的适用情况
自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁
信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。另外格外注意一点:自旋锁不能递归使用。
四、spinlock与mutex对比
spinlock不会使线程状态发生切换,mutex在获取不到锁的时候会选择sleep
mutex获取锁分为两阶段,第一阶段在用户态采用spinlock锁总线的方式获取一次锁,如果成功立即返回;否则进入第二阶段,调用系统的futex锁去sleep,当锁可用后被唤醒,继续竞争锁。
Spinlock优点:没有昂贵的系统调用,一直处于用户态,执行速度快
Spinlock缺点:一直占用cpu,而且在执行过程中还会锁bus总线,锁总线时其他处理器不能使用总线
Mutex优点:不会忙等,得不到锁会sleep
Mutex缺点:sleep时会陷入到内核态,需要昂贵的系统调用
五、关于spinlock的定义以及相应的API
自旋锁定义: linux/Spinlock.h
typedef struct spinlock { union { //联合 struct raw_spinlock rlock; #ifdef CONFIG_DEBUG_LOCK_ALLOC #define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map)) struct{ u8 __padding[LOCK_PADSIZE]; struct lockdep_map dep_map; }; #endif }; } spinlock_t;
定义和初始化
spinlock_t my_lock = SPIN_LOCK_UNLOCKED; void spin_lock_init(spinlock_t *lock);
自旋锁操作
//加锁一个自旋锁函数 void spin_lock(spinlock_t *lock); //获取指定的自旋锁 void spin_lock_irq(spinlock_t *lock); //禁止本地中断获取指定的锁 void spin_lock_irqsave(spinlock_t *lock, unsigned long flags); //保存本地中断的状态,禁止本地中断,并获取指定的锁 void spin_lock_bh(spinlock_t *lock) //安全地避免死锁, 而仍然允许硬件中断被服务 //释放一个自旋锁函数 void spin_unlock(spinlock_t *lock); //释放指定的锁 void spin_unlock_irq(spinlock_t *lock); //释放指定的锁,并激活本地中断 void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags); //释放指定的锁,并让本地中断恢复到以前的状态 void spin_unlock_bh(spinlock_t *lock); //对应于spin_lock_bh //非阻塞锁 int spin_trylock(spinlock_t *lock); //试图获得某个特定的自旋锁,如果该锁已经被争用,该方法会立刻返回一个非0值, //而不会自旋等待锁被释放,如果成果获得了这个锁,那么就返回0. int spin_trylock_bh(spinlock_t *lock); //这些函数成功时返回非零( 获得了锁 ), 否则 0. 没有"try"版本来禁止中断. //其他 int spin_is_locked(spinlock_t *lock); //和try_lock()差不多
六、nginx中的实现
在nginx中 spinlock的使用场景是,nginx借助spinlock的技术,实现了用户态的进程间的mutex. 由于spinlock是阻塞的
#define ngx_shmtx_lock(mtx) ngx_spinlock((mtx)->lock, ngx_pid, 1024)
具体分析一个spinlock的开源实现
// 输入参数 // lock:一个整形变量的指针 // value:将lock设置新的值 // spin: 自旋的次数. 该值越大会尝试更多次获得锁. 然后才会转入让内核调度线程暂时让出cpu. void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin) { #if (NGX_HAVE_ATOMIC_OPS) ngx_uint_t i, n; for ( ;; ) { if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) { return; } // 为何只在多个cpu的时候才多尝试spin几次 // 呵呵,很简单,如果是单核的话,既然自己没有拿到锁,那说明别的线程/进程正在使用锁,就这么一个cpu,咱就不占着自旋了,否则别人没机会得到cpu,更不会释放锁了. if (ngx_ncpu > 1) { for (n = 1; n < spin; n <<= 1) { // 空转的时间随着n的变大而变大 for (i = 0; i < n; i++) { ngx_cpu_pause(); // 在空转的同时, 降低cpu功耗,提高效率 } if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) { return; } } } // 已经尝试这么久了还没有得到锁, 让cpu忙别人的事情吧. 让出cpu等待一下. ngx_sched_yield(); } #else #if (NGX_THREADS) #error ngx_spinlock() or ngx_atomic_cmp_set() are not defined ! #endif #endif } #if (NGX_HAVE_SCHED_YIELD) #define ngx_sched_yield() sched_yield() #else #define ngx_sched_yield() usleep(1) #endif
7、PHP扩展中的实现
/* GCC support */ #include <stdlib.h> #include "spinlock.h" extern int ncpu; void spin_lock(atomic_t *lock, int which) { int i, n; for ( ;; ) { if (*lock == 0 &&. __sync_bool_compare_and_swap(lock, 0, which)) { return; } if (ncpu > 1) { for (n = 1; n < 129; n << 1) { for (i = 0; i < n; i++) { __asm("pause"); } if (*lock == 0 &&. __sync_bool_compare_and_swap(lock, 0, which)) { return; } } } sched_yield(); } } void spin_unlock(atomic_t *lock, int which) { __sync_bool_compare_and_swap(lock, which, 0); }
参考文章
https://www.ibm.com/developerworks/cn/linux/l-cn-mcsspinlock/
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html
http://www.360doc.com/content/11/0302/14/3038654_97459411.shtml
http://ifeve.com/practice-of-using-spinlock-instead-of-mutex/
http://www.cnblogs.com/FrankTan/archive/2010/12/11/1903377.html