使用yeild實現spinlock

FreeeLinux發表於2017-05-10

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


原文:http://www.thinksaas.cn/topics/0/443/443697.html

相關文章