自旋鎖

1、什麼是自旋鎖編程

  一直覺得自旋鎖也是用於多線程互斥的一種鎖,原來不是!安全

  自旋鎖是專爲防止多處理器併發(實現保護共享資源)而引入的一種鎖機制。自旋鎖與互斥鎖比較相似,它們都是爲了解決對某項資源的互斥使用。不管是互斥鎖,仍是自旋鎖,在任什麼時候刻,最多隻能有一個保持者,也就說,在任什麼時候刻最多隻能有一個執行單元得到鎖。可是二者在調度機制上略有不一樣。對於互斥鎖,若是資源已經被佔用,資源申請者只能進入睡眠狀態。可是自旋鎖不會引發調用者睡眠,若是自旋鎖已經被別的執行單元保持,調用者就一直循環在那裏看是否該自旋鎖的保持者已經釋放了鎖,「自旋」一詞就是所以而得名。自旋鎖在內核中大量應用於中斷處理等部分(對於單處理器來講,防止中斷處理中的併發可簡單採用關閉中斷的方式,即在標誌寄存器中關閉/打開中斷標誌位,不須要自旋鎖)。數據結構

  自旋鎖的初衷就是:在短時間間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖從新可用的期間進行自旋(特別浪費處理器時間),因此自旋鎖不該該被持有時間過長。若是須要長時間鎖定的話, 最好使用信號量。多線程

  自旋鎖只有在內核可搶佔或SMP(多處理器)的狀況下才真正須要,在單CPU且不可搶佔的內核下,自旋鎖的全部操做都是空操做。併發

2、自旋鎖的缺陷函數

  自旋鎖是一種比較低級的保護數據結構或代碼片斷的原始方式,這種鎖可能存在兩個問題:測試

(1)死鎖。試圖遞歸地得到自旋鎖必然會引發死鎖:例如遞歸程序的持有實例在第二個實例循環,以試圖得到相同自旋鎖時,就不會釋放此自旋鎖。因此,在遞歸程序中使用自旋鎖應遵照下列策略:遞歸程序決不能在持有自旋鎖時調用它本身,也決不能在遞歸調用時試圖得到相同的自旋鎖。此外若是一個進程已經將資源鎖定,那麼,即便其它申請這個資源的進程不停地瘋狂「自旋」,也沒法得到資源,從而進入死循環。
(2)過多佔用cpu資源。若是不加限制,因爲申請者一直在循環等待,所以自旋鎖在鎖定的時候,若是不成功,不會睡眠,會持續的嘗試,單cpu的時候自旋鎖會讓其它process動不了。所以,通常自旋鎖實現會有一個參數限定最多持續嘗試次數。超出後,自旋鎖放棄當前time slice,等下一次機會。 ui

  因而可知,自旋鎖比較適用於鎖使用者保持鎖時間比較短的狀況。正是因爲自旋鎖使用者通常保持鎖時間很是短,所以選擇自旋而不是睡眠是很是必要的,自旋鎖的效率遠高於互斥鎖。信號量和讀寫信號量適合於保持時間較長的狀況,它們會致使調用者睡眠,所以只能在進程上下文使用,而自旋鎖適合於保持時間很是短的狀況,它能夠在任何上下文使用。 線程

3、Linux環境下的自旋鎖rest

  自旋鎖的實現基於共享變量。一個線程經過給共享變量設置一個值來獲取鎖,其餘等待線程查詢共享變量是否爲0來肯定鎖現是否可用,而後在忙等待的循環中「自旋」直到鎖可用爲止。自旋鎖的狀態值爲1表示解鎖狀態,說明有1個資源可用;0或負值表示加鎖狀態,0說明可用資源數爲0。Linux內核爲通用自旋鎖提供了API函數初始化、測試和設置自旋鎖。API函數功能說明以下表所示:

宏定義 功能說明
spin_lock_init(lock) 初始化自旋鎖,將自旋鎖設置爲1,表示有一個資源可用。
spin_is_locked(lock) 若是自旋鎖被置爲1(未鎖),返回0,不然返回1。
spin_unlock_wait(lock) 等待直到自旋鎖解鎖(爲1),返回0;不然返回1。
spin_trylock(lock) 嘗試鎖上自旋鎖(置0),若是原來鎖的值爲1,返回1,不然返回0。
spin_lock(lock) 循環等待直到自旋鎖解鎖(置爲1),而後,將自旋鎖鎖上(置爲0)。
spin_unlock(lock) 將自旋鎖解鎖(置爲1)。
spin_lock_irqsave(lock, flags) 循環等待直到自旋鎖解鎖(置爲1),而後,將自旋鎖鎖上(置爲0)。關中斷,將狀態寄存器值存入flags。
spin_unlock_irqrestore(lock, flags) 將自旋鎖解鎖(置爲1)。開中斷,將狀態寄存器值從flags存入狀態寄存器。
spin_lock_irq(lock) 循環等待直到自旋鎖解鎖(置爲1),而後,將自旋鎖鎖上(置爲0)。關中斷。
spin_unlock_irq(lock) 將自旋鎖解鎖(置爲1)。開中斷。
spin_unlock_bh(lock) 將自旋鎖解鎖(置爲1)。開啓底半部的執行。
spin_lock_bh(lock) 循環等待直到自旋鎖解鎖(置爲1),而後,將自旋鎖鎖上(置爲0)。阻止軟中斷的底半部的執行。

  在實際編程中,什麼時候使用spin_lock,什麼時候使用spin_lock_irq呢?這二者有點區別。

(1)spin_lock

  spin_lock 的實現關係爲:spin_lock -> raw_spin_lock -> _raw_spin_lock -> __raw_spin_lock ,而__raw_spin_lock 的實現爲:

static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
    preempt_disable();
    spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
    LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

 (2)spin_lock_irq

  spin_lock_irq 的實現關係爲:spin_lock_irq -> raw_spin_lock_irq -> _raw_spin_lock_irq -> __raw_spin_lock_irq,而__raw_spin_lock_irq 的實現爲:

static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
    local_irq_disable();
    preempt_disable();
    spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
    LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

  因而可知,這二者之間只有一個差異:是否調用local_irq_disable()函數, 便是否禁止本地中斷。這二者的區別能夠總結爲:

  • 在任何狀況下使用spin_lock_irq都是安全的。由於它既禁止本地中斷,又禁止內核搶佔。
  • spin_lock比spin_lock_irq速度快,可是它並非任何狀況下都是安全的。
  舉例來講明:進程A中調用了spin_lock(&lock)而後進入臨界區,此時來了一箇中斷(interrupt),該中斷也運行在和進程A相同的CPU上,而且在該中斷處理程序中恰巧也會spin_lock(&lock)試圖獲取同一個鎖。因爲是在同一個CPU上被中斷,進程A會被設置爲TASK_INTERRUPT狀態,中斷處理程序沒法得到鎖,會不停的忙等,因爲進程A被設置爲中斷狀態,schedule()進程調度就沒法再調度進程A運行,這樣就致使了死鎖!可是若是該中斷處理程序運行在不一樣的CPU上就不會觸發死鎖。 由於在不一樣的CPU上出現中斷不會致使進程A的狀態被設爲TASK_INTERRUPT,只是換出。當中斷處理程序忙等被換出後,進程A仍是有機會得到CPU,執行並退出臨界區。因此在使用spin_lock時要明確知道該鎖不會在中斷處理程序中使用。
相關文章
相關標籤/搜索