zoukankan      html  css  js  c++  java
  • 【linux】spinlock 的实现

    一、什么是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

  • 相关阅读:
    UVa532 Dungeon Master 三维迷宫
    6.4.2 走迷宫
    UVA 439 Knight Moves
    UVa784 Maze Exploration
    UVa657 The die is cast
    UVa572 Oil Deposits DFS求连通块
    UVa10562 Undraw the Trees
    UVa839 Not so Mobile
    327
    UVa699 The Falling Leaves
  • 原文地址:https://www.cnblogs.com/chenpingzhao/p/5043746.html
Copyright © 2011-2022 走看看