Swoole源碼學習記錄(五)——鎖和信號(二)

Swoole版本:1.7.4-stable

二.Mutex互斥鎖

接下來是Mutex(互斥鎖)。Swoole的Mutex實現是基於pthread_mutex*方法族實現的,Rango用一個swMutex結構體封裝了mutex的兩個屬性,結構體定義如下:

//互斥鎖
typedef struct _swMutex
{
         pthread_mutex_t _lock;           // 互斥鎖對象
         pthread_mutexattr_t attr;      // 互斥鎖的屬性
}swMutex;


創建一個互斥鎖的函數聲明在swoole.h文件的 536 行,聲明如下:

int swMutex_create(swLock *lock, int use_in_process);

其中lock爲鎖對象,use_in_process用於標記該鎖是否用於進程之間。該函數的具體定義在Mutex.c文件中,其核心代碼如下:

 

         lock->type = SW_MUTEX;
         pthread_mutexattr_init(&lock->object.mutex.attr);
         if (use_in_process == 1)
         {
                   pthread_mutexattr_setpshared(&lock->object.mutex.attr,PTHREAD_PROCESS_SHARED);
         }
         if ((ret =pthread_mutex_init(&lock->object.mutex._lock,&lock->object.mutex.attr)) < 0)
         {
                   return SW_ERR;
         }

源碼解釋:設置鎖的類型爲SW_MUTEX,並調用pthread_mutexattr_init函數初始化mutex的屬性結構體。如果該進程用於進程之間,則調用pthread_mutexattr_setpshared函數設置該鎖的共享屬性爲PTHREAD_PROCESS_SHARED(進程間共享)。接着調用pthread_mutex_init函數初始化互斥鎖。

 

這裏要重點分析三個標紅的函數。這三個函數都屬於pthread_mutex*方法族,用於操作mutex以及其attr屬性。

1.      pthread_mutexattr_init方法用於初始化一個pthread_mutexattr_t結構體,並默認設置其pshared屬性爲PTHREAD_PROCESS_PRIVATE(表示可以在進程內使用該互斥鎖)。

2.      pthread_mutexattr_setpshared方法用來設置互斥鎖變量的作用域。PTHREAD_PROCESS_SHARED表示該互斥鎖可被多個進程共享使用(前提是該互斥鎖是在共享內存中創建)。PTHREAD_PROCESS_PRIVATE表示該互斥鎖僅能被那些由同一個進程創建的線程處理。

3.    pthread_mutex_init方法以動態方式創建互斥鎖,並通過參數attr指定互斥鎖屬性。如果參數attr爲空,則默認創建快速互斥鎖。

swMutex的其他操作函數swMutex_lock、swMutex_unlock、swMutex_trylock、free分別對應pthread_mutex_lock、pthread_mutex_unlock、pthread_mutex_trylock、pthread_mutex_destroy,用於加鎖、解鎖、嘗試加鎖、銷燬swMutex。

 

三.RWLock讀寫鎖

讀寫鎖的內容其實與互斥鎖相差不大,區別僅在於讀寫鎖有讀鎖、寫鎖兩個區別,同時底層調用的是pthread_rwlock*系列函數。讀寫鎖swRWLock結構體在swoole.c文件的 398 – 404 行聲明,如下:

//讀寫鎖
typedef struct  _swRWLock
{
         pthread_rwlock_t _lock;          // 讀寫鎖對象
         pthread_rwlockattr_t attr;     // 讀寫鎖屬性
 
} swRWLock;

創建一個讀寫鎖的函數聲明在swoole.h文件的 534 行,聲明如下:

int swRWLock_create(swLock *lock, int use_in_process);

其中lock爲鎖對象,use_in_process用於標記該鎖是否用於進程之間。該函數的具體定義在RWLock.c文件中,其核心代碼如下:

         lock->type = SW_RWLOCK;
         if(use_in_process == 1)
         {
                   pthread_rwlockattr_setpshared(&lock->object.rwlock.attr,PTHREAD_PROCESS_SHARED);
         }
         if((ret =pthread_rwlock_init(&lock->object.rwlock._lock, &lock->object.rwlock.attr))< 0)
         {
                   return SW_ERR;
         }

源碼解釋:設置鎖類型爲SW_RWLOCK,如果該鎖用於進程之間,則調用pthread_rwlockattr_setpshared函數設置該鎖的共享屬性爲PTHREAD_PROCESS_SHARED(進程間共享)。接着調用pthread_rwlock_init初始化該讀寫鎖。

 

這裏重點分析兩個標紅函數。這兩個函數屬於pthread_rwlock*方法族,用於操作rwlock以及其attr屬性。

1.      pthread_rwlockattr_setpshared用於設置一個pthread_rwlockattr_t的屬性,和mutex一樣有PTHREAD_PROCESS_SHARED和PTHREAD_PROCESS_PRIVATE兩個值。

2.      pthread_rwlock_init用於初始化一個pthread_rwlock_t結構體,創建一個讀寫鎖,並通過attr設置其屬性。

(實際上rwlock也有pthread_rwlockattr_init方法,不知道爲什麼這裏沒有調用。所有pthread_rwlock*相關的函數請參考http://blog.163.com/software_ark/blog/static/175614594201181665330631/

剩下的就是通過調用pthread_rwlock的相關操作函數如pthread_rwlock_rdlock、pthread_rwlock_wrlock來創建讀鎖、寫鎖以及解鎖。大家可以直接看RWLock.c文件中的定義,在此爲節約篇幅不再敘述。

四.SpinLock自旋鎖

首先需要說明一下什麼是自旋鎖。Spinlock本質上也是一種互斥鎖,它和mutex的區別在於Spinlock是通過busy_wait_loop方式來獲得鎖,不會使線程狀態發生切換(用戶態->內核態),因此減少了系統調用,執行速度快。但缺點也是有的,Spinlock會一直佔用cpu,導致cpu busy飆高,因此要謹慎使用Spinlock。

SpinLock自旋鎖的特殊性在於其只有一個pthread_spinlock_t鎖對象,沒有對應的屬性結構體。其swSpinLock結構體在swoole.c文件中 406 – 412 行聲明,如下:

//自旋鎖
#ifdef HAVE_SPINLOCK
typedef struct _swSpinLock
{
         pthread_spinlock_tlock_t;      // 自旋鎖對象
} swSpinLock;
#endif

創建一個自旋鎖的函數聲明在swoole.h文件的 534 行,聲明如下:

int swSpinLock_create(swLock *object, int spin);

其中lock爲鎖對象,spin(爲何不繼續使用use_in_process……)用於標記該鎖是否用於進程之間。該函數的具體定義在SpinLock.c文件中,其核心代碼如下:

         lock->type =SW_SPINLOCK;
         if((ret = pthread_spin_init(&lock->object.spinlock.lock_t,use_in_process)) < 0)
         {
                   return-1;
         }

源碼解釋:設置鎖類型爲SW_SPINLOCK,調用pthread_spin_init初始化一個Spinlock對象。

1.      pthread_spin_init用於初始化一個pthread_spinlock_t對象,第二個參數用於指定該spinlock是否可被進程間共享。

其他操作均爲直接調用pthread_spin*方法族的相關函數,具體內容請看SpinLock.c文件。

五.原子鎖

首先我說明一下什麼是原子操作。所謂原子操作,就是該操作絕不會在執行完畢前被任何其他任務或事件打斷,也就說,它的最小的執行單位,不可能有比它更小的執行單位。通常情況下,原子操作都是用於資源計數。

原子鎖就是用來保證操作的原子性。Swoole中自定義了一個原子類型atomic_t,其聲明在atomic.h文件中的14行,這個類型在不同位數的操作系統裏有不同的長度。同時,swoole使用了gcc提供的__sync_*系列的build-in函數來提供加減和邏輯運算的原子操作。這些函數的聲明在atomic.h文件的16 – 20行,如下:

#define sw_atomic_cmp_set(lock, old, set) __sync_bool_compare_and_swap(lock, old, set)
#define sw_atomic_fetch_add(value,add)   __sync_fetch_and_add(value, add)
#define sw_atomic_fetch_sub(value,sub)   __sync_fetch_and_sub(value, sub)
#define sw_atomic_memory_barrier()        __sync_synchronize()
#define sw_atomic_cpu_pause()             __asm__ ("pause")

這裏依次介紹5個函數的作用:

1.      sw_atomic_cmp_set(lock, old,set) 如果lock的值等於old的值,將set的值寫入lock。如果相等並寫入成功則返回true

2.      sw_atomic_fetch_add(value, add)將value的值增加add,返回增加前的值

3.      sw_atomic_fetch_sub(value, sub)將value的值減去sub,返回減去前的值

4.      sw_atomic_memory_barrier() 發出一個fullbarrier。保證指令執行的順序合理

5.      sw_atomic_cpu_pause() 這個函數調用的是__asm__(“pause”),通過一番查詢,這個調用能讓cpu等待一段時間,這個時間由處理器定義。

 

Swoole另外聲明瞭一個結構體swAtomicLock來封裝原子鎖,該結構體在swoole.h文件中 414 – 419行聲明,如下:

//原子鎖Lock-Free
typedef struct _swAtomicLock
{
         atomic_tlock_t;
         uint32_tspin;
} swAtomicLock;

其中lock_t爲原子鎖對象,spin爲一個原子鎖可以等待的次數(爲2的n次冪)。

原子鎖的全部操作函數聲明在swoole.h文件的541 – 544行,如下:

int swAtomicLock_create(swLock *object, intspin);
sw_inline int swAtomicLock_lock(swLock* lock);
sw_inline int swAtomicLock_unlock(swLock* lock);
sw_inline int swAtomicLock_trylock(swLock* lock);

這裏着重介紹swAtomicLock_lock方法,其核心代碼如下:

 

         atomic_t *atomic= &lock->object.atomlock.lock_t;
         uint32_ti, n;
         while(1)
         {
                   if(*atomic == 0 && sw_atomic_cmp_set(atomic, 0, 1))
                   {
                            returnSW_OK;
                   }
                   if(SW_CPU_NUM > 1)
                   {
                            for(n = 1; n < lock->object.atomlock.spin; n <<= 1)
                            {
                                     for(i = 0; i < n; i++)
                                     {
                                               sw_atomic_cpu_pause();
                                     }
 
                                     if(*atomic == 0 && sw_atomic_cmp_set(atomic, 0, 1))
                                     {
                                               returnSW_OK;
                                     }
                            }
                   }
                   swYield();
         }
         returnSW_ERR;

源碼解釋:首先獲得atomic_t對象的指針,接着進入一個無限循環。在該循環裏,首先判斷atomic的值是否爲0,並嘗試將其賦值爲1,如果成功,則直接返回。若前兩個判斷條件不成立,接着判斷CPU數目是否大於1(多核),如果是則進入循環,循環變量爲n,每次循環內先執行n次sw_atomic_cpu_pause(),隨後再次嘗試設置atomic的值爲1。每次循環結束後將n的值左移一位(值*2,這就是爲何spin的值要是2的冪)。如果CPU的數目爲1或者直到內循環結束也沒有設置成功atomic,就調用swYield方法交出線程的使用權。

這裏就是在不停的嘗試申請atomic鎖,如果失敗就等待一段時間後再次申請,每次失敗後等待的時間會加長。

六.信號量

Swoole的Semaphore信號量的實現是基於Linux的semget、semop和semctl函數實現的。結構體swSem用於封裝信號量相關的屬性,其聲明在swoole.h的421-427行,如下:

//信號量
typedef struct _swSem
{
         key_tkey;         // 關鍵字,通常由ftok()返回
         intsemid;         // 信號量id
         intlock_num;   //
} swSem;

創建一個swSem的函數聲明在swoole.h文件的535行,如下:

int swSem_create(swLock *lock, key_t key,int n);

其中lock爲信號量對象,key爲信號量關鍵字,n爲該信號量擁有的資源數。

該函數具體定義在Semaphore.c文件內,其核心代碼如下:

         lock->type =SW_SEM;
         if((ret = semget(key, n, IPC_CREAT | 0666)) < 0)
         {
                   returnSW_ERR;
         }
         lock->object.sem.semid= ret;
         lock->object.sem.lock_num= 0;

源碼解釋:設置鎖類型爲SW_SEM,並調用semget函數創造一個信號量,並返回信號的id。設置信號量id並設置lock_num爲0。

swSem的操作函數共有三個:swSem_lock,swSem_unlock,swSem_free。分別對應加鎖、解鎖和銷燬。

swSem_lock的核心代碼如下:

         struct sembuf sem;
         sem.sem_flg= SEM_UNDO;
         sem.sem_num= lock->object.sem.lock_num;
         //sem.sem_op = 1; 此行爲原始代碼,經作者確認爲bug
         sem.sem_op = -1;
         return semop(lock->object.sem.semid, &sem, 1);

源碼解釋:創建一個struct sembuf 結構體,設置sem_flg爲SEM_UNFO(程序結束時(不論正常或不正常),保證信號值會被重設爲semop()調用前的值),指定操作的信號量爲lock_num,設置sem_op爲-1(請求資源),最後調用semop函數操作信號量。

swSem_unlock的核心代碼如下:

         struct sembuf sem;
         sem.sem_flg= SEM_UNDO;
         sem.sem_num= lock->object.sem.lock_num;
         //sem.sem_op = -1; 此行爲原始代碼,經作者確認爲bug
         sem.sem_op= 1;
         return semop(lock->object.sem.semid, &sem, 1);

源碼解釋:創建一個struct sembuf 結構體,設置sem_flg爲SEM_UNFO(程序結束時(不論正常或不正常),保證信號值會被重設爲semop()調用前的值),指定操作的信號量爲lock_num,設置sem_op爲1(釋放資源),最後調用semop函數操作信號量。

swSem_free函數就是調用semctl方法釋放信號量。

七.Cond條件變量

條件變量的用處是使線程睡眠等待某種條件出現後喚醒線程,是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個是線程等待“條件成立”而掛起,另一個是線程使“條件成立”併發出信號。爲了防止競爭,條件變量通常與一個互斥鎖結合在一起。

Swoole中,條件變量的實現使用了pthread_cond*方法族,其封裝結構體swCond聲明在swoole.c文件的451 - 461行,如下:

//Cond
typedef struct _swCond
{
         swLocklock;                                 // 互斥鎖
         pthread_cond_tcond;              // 條件變量對象
 
         int(*wait)(struct _swCond *object);                                   //四個操作函數
         int(*timewait)(struct _swCond *object,long,long);
         int(*notify)(struct _swCond *object);
         int(*broadcast)(struct _swCond *object);
} swCond;

swCond的相關操作函數共六個,聲明在swoole.c文件中的546 – 551行,如下:

int swCond_create(swCond *cond);
int swCond_notify(swCond *cond);
int swCond_broadcast(swCond *cond);
int swCond_timewait(swCond *cond, long sec,long nsec);
int swCond_wait(swCond *cond);
void swCond_free(swCond *cond);

這六個函數均在Cond.c文件中被定義。

1.      swCond_create核心代碼:

         if(pthread_cond_init(&cond->cond, NULL) < 0)
         {
                   swWarn("pthread_cond_initfail. Error: %s [%d]", strerror(errno), errno);
                   returnSW_ERR;
         }
         if(swMutex_create(&cond->lock, 0) < 0)
         {
                   returnSW_ERR;
         }

源碼解釋:調用pthread_cond_init創建並初始化一個條件變量,並創建對應的互斥鎖。

2.      swCond_notify方法調用pthread_cond_signal方法,向另一個正處於阻塞等待狀態的線程發信號,使其脫離阻塞狀態。需要注意的是,如果有多個線程正在等待,則根據優先級的高低決定由哪個線程收到信號;若優先級相同,則優先讓等待時間最長的線程執行。

3.      swCond_broadcast方法調用pthread_cond_broadcast方法,向所有正處於阻塞等待狀態的線程發出信號使其脫離阻塞狀態。

4.      swCond_timewait方法調用pthread_cond_timedwait方法,計時等待其他線程發出信號,等待時間由參數long sec,long nsec指定。

5.      swCond_wait方法調用pthread_cond_wait方法,等待其他線程發出信號。

6.      swCond_free方法調用pthread_cond_destroy方法銷燬信號變量,並銷燬互斥鎖。

 

至此,swoole全部的鎖和信號已分析完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章