使用yeild實現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. 但必須讓出很小一會. 否則就不叫自旋鎖了
三、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
相關文章
- C# yeild使用C#
- PostgreSQL 原始碼解讀(218)- spinlock的實現SQL原始碼
- 自旋鎖spinlock
- spinlock和mutex選用方法Mutex
- Nginx 原始碼完全剖析(11)ngx_spinlockNginx原始碼
- LiteOS:SpinLock自旋鎖及LockDep死鎖檢測
- java多執行緒基礎篇(wait、notify、join、sleep、yeild方法)Java執行緒AI
- 使用 Block 實現 KVOBloC
- Golang 實現 Redis(5): 使用跳錶實現 SortedSetGolangRedis
- 使用 Swift 實現堆排序Swift排序
- KVO使用及實現原理
- 使用RxJava實現快取RxJava快取
- 使用Kafka實現事件溯源Kafka事件
- 如何使用 redis 實現限流Redis
- 使用 Java實現mTLS呼叫JavaTLS
- 使用PreparedStatement實現CRUD操作
- 使用 libffi 實現 AOP
- 使用Webcam實現拍照功能Web
- 使用setTimeout實現setInterval
- 使用Akka實現Reactive DDDReact
- 使用 Flask 實現 RESTful APIFlaskRESTAPI
- 使用Memcached實現Session共享Session
- 使用者管理實現
- 使用 FastText 實現詞嵌入AST
- 使用 CSS 實現透明效果CSS
- 使用Oracle實現實時通訊(轉)Oracle
- 使用 jQuery 實現分頁功能jQuery
- 使用正則實現 getType方法
- 使用async實現非同步控制非同步
- 使用 Swift 實現歸併排序Swift排序
- PHP 使用 Redis 實現分頁PHPRedis
- 使用redis實現互粉功能Redis
- 使用LayUI實現AJAX分頁UI
- Nginx使用Lua模組實現WAFNginx
- 使用ConcurrentHashMap實現快取HashMap快取
- 使用leancloud實現迭代查詢Cloud
- 使用PrepareStatement實現批量插入操作REST
- layui使用者新增實現UI