一、什么是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. 但必须让出很小一会. 否则就不叫自旋锁了

如何让出cpu,却有可以很快的回来? 内核提供了 sched_yield()函数,sched_yield()主要功能: 简单的讲,可以使用另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序,如果系统不支持sched_yield, nginx被迫使用了usleep()休息1u秒.

三、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

【linux】spinlock 的实现相关推荐

  1. 从CPU cache一致性的角度看Linux spinlock的不可伸缩性(non-scalable)

    凌晨一点半的深圳雨夜: 豪雨当夜惊起有人赏,笑叹落花无声空飘零. 喜欢这种豪雨,让人兴奋.惊起作文以呜呼之感叹! 引用上一篇文章: 优化多核CPU的TCP新建连接性能–重排spinlock:https ...

  2. linux spinlock/rwlock/seqlock原理剖析(基于ARM64)

    背景 Kernel版本:4.14 ARM64处理器,Contex-A53,双核 使用工具:Source Insight 3.5, Visio 1. 概述 吹起并发机制研究的进攻号角了! 作为第一篇文章 ...

  3. 一文解析linux spinlock/rwlock/seqlock原理(基于ARM64)

    说明: Kernel版本:4.14 ARM64处理器,Contex-A53,双核 使用工具:Source Insight 3.5, Visio 1. 概述 吹起并发机制研究的进攻号角了! 作为第一篇文 ...

  4. linux spinlock mutex semaphore

    信号量在内核中的定义如下: struct semaphore {raw_spinlock_t lock;///自旋锁unsigned int count;///count=1时可进行互斥操作struc ...

  5. Linux 内核同步(二):自旋锁(Spinlock)

    自旋锁 内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择: 一个是原地等待 一个是挂起当前进程,调度其他进程执行(睡眠) Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是&quo ...

  6. linux kernel的spinlock代码导读和分析

    文章目录 一.代码阅读分析 0.spin lock调用流程图 1.再kernel中调用spi_lock()或spin_unlock函数 2.调用raw_spin_lock()和raw_spin_unl ...

  7. Linux同步原语系列-spinlock及其演进优化

    1. 引言 通常我们的说的同步其实有两个层面的意思: 一个是线程间的同步,主要是为了按照编程者指定的特定顺序执行: 另外一个是数据的同步,主要是为了保存数据. 为了高效解决同步问题,前人抽象出同步原语 ...

  8. linux驱动22:自旋锁spinlock

    自旋锁: 和信号量不同的是自旋锁可在不能休眠的代码中使用,如中断处理例程.在正确使用时,自旋锁通常比信号量具有更高的性能. 如果锁可用,则锁定位被设置,代码继续进入临界区:相反则代码进入忙循环并重复检 ...

  9. Linux内核自旋锁使用笔记

    Reference: spin_lock_bh()与spin_unlock_bh() Linux内核自旋锁 Linux自旋锁 Spinlock - Wikipedia, the free encycl ...

最新文章

  1. Java黑皮书课后题第4章:*4.18(学生的专业和年级)编程一个程序,提示用户输入两个字符,显示这两个字符代表的专业以及年级,第一个字符表示专业,第二个是一个数字字符1、2、3、4,输出对应结果
  2. (*长期更新)软考网络工程师学习笔记——Linux操作系统中的vi/vim 编辑器详解
  3. java boolean 多线程_JAVA多线程两个实用的辅助类(CountDownLatch和AtomicBoolean)
  4. 安装百分之80卡住_新车买回来要不要安装发动机护板呢
  5. 字母三角形c语言ABBBCCCCC,C语言输出ABBBCCCCCDDDDDDDCCCCCBBBA
  6. WinDbg分析dump文件排查bug
  7. 搭建 Apache Jmeter 分布式压测与监控
  8. ES6 面向对象编程
  9. linux内存源码分析 - 内存池
  10. 坚果云 linux 脚本,深度操作系统Deepin安装坚果云
  11. 解决SecoClient接收返回码超时
  12. 随机点名和抽题软件(可支持ppt扩展模式使用)
  13. 如何在Linux中删除符号链接?
  14. 珠宝订货(订单)系统与ERP实现库存信息同步的实现方案分享
  15. pcb板中字母P代表什么
  16. 利用UltrISO将gho文件制作可引导iso
  17. 《人机交互:软件工程视角》期末复习提纲
  18. UOS服务器操作系统下载RPM包和依赖
  19. android降噪算法,Android主动降噪功能
  20. VR购物为实体零售开启科技助力

热门文章

  1. leecode第一百四十八题(排序链表)
  2. 一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少...
  3. js中的数组对象排序(方法sort()详细介绍)
  4. RHEL环境下调试Shell脚本时遇到字符串转换整数的问题
  5. 神经网络总结(初稿)
  6. 【转】细数中国十大名校的IT牛人
  7. 留存率提高20%+,如何通过产品迭代做用户增长?
  8. 百度关闭新闻源背后的13个趋势风口
  9. 【创业】创业公司股权架构设计注意事项
  10. ServletContextListener接口用法