程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 【linux】spinlock 的實現,linuxspinlock實現

【linux】spinlock 的實現,linuxspinlock實現

編輯:C++入門知識

【linux】spinlock 的實現,linuxspinlock實現


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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved