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全部的鎖和信號已分析完成。