spinlock又稱自旋鎖,是實現保護共享資源而提出一種鎖機制。自旋鎖與互斥鎖比較類似,都是為了解決對某項資源的互斥使用
無論是互斥鎖,還是自旋鎖,在任何時刻,最多只能有一個保持者,只能有一個執行單元獲得鎖。但是兩者在調度機制上略有不同。對於互斥鎖,如果資源已經被占用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那裡看是否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名
跟互斥鎖一樣,一個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源後,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那麼將立即得到鎖;如果在獲取自旋鎖時鎖已經有保持者,那麼獲取鎖操作將自旋在那裡,直到該自旋鎖的保持者釋放了鎖。自旋鎖是一種比較低級的保護數據結構或代碼片段的原始方式,這種鎖可能存在兩個問題:死鎖和過多占用cpu資源
自旋鎖比較適用於鎖使用者保持鎖時間比較短的情況。正是由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖
信號量和讀寫信號量適合於保持時間較長的情況,它們會導致調用者睡眠,因此只能在進程上下文使用,而自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用。如果被保護的共享資源只在進程上下文訪問,使用信號量保護該共享資源非常合適,如果對共享資源的訪問時間非常短,自旋鎖也可以。但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷),就必須使用自旋鎖。自旋鎖保持期間是搶占失效的,而信號量和讀寫信號量保持期間是可以被搶占的。自旋鎖只有在內核可搶占或SMP(多處理器)的情況下才真正需要,在單CPU且不可搶占的內核下,自旋鎖的所有操作都是空操作。另外格外注意一點:自旋鎖不能遞歸使用。
spinlock不會使線程狀態發生切換,mutex在獲取不到鎖的時候會選擇sleep
mutex獲取鎖分為兩階段,第一階段在用戶態采用spinlock鎖總線的方式獲取一次鎖,如果成功立即返回;否則進入第二階段,調用系統的futex鎖去sleep,當鎖可用後被喚醒,繼續競爭鎖。
Spinlock優點:沒有昂貴的系統調用,一直處於用戶態,執行速度快
Spinlock缺點:一直占用cpu,而且在執行過程中還會鎖bus總線,鎖總線時其他處理器不能使用總線
Mutex優點:不會忙等,得不到鎖會sleep
Mutex缺點:sleep時會陷入到內核態,需要昂貴的系統調用
自旋鎖定義: 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中 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
/* 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