鎖從功能上劃分主要有互斥鎖和讀寫鎖。
不主動(dòng)釋放CPU的鎖是spinlock,可以用于競爭較少并且臨界區(qū)較小的場景,也可以用于中斷上下文中。
鎖一般用信號量、原子操作(cas)或文件鎖來實(shí)現(xiàn)。
nginx中實(shí)現(xiàn)了互斥鎖和讀寫鎖以及spinlock。
互斥鎖
加鎖:

lock是一個(gè)原子變量,0表示沒有上鎖,ngx_pid表示被對應(yīng)的進(jìn)程加鎖,如果當(dāng)前被其它進(jìn)程加鎖,說明其它進(jìn)程正在執(zhí)行臨界區(qū)內(nèi)的代碼,不會(huì)馬上釋放鎖,所以會(huì)根據(jù)spin一會(huì)兒再嘗試加鎖,如果不spin而一直不斷嘗試訪問共享變量將會(huì)增加核間通信的負(fù)擔(dān),ngx_cpu_pause()防止循環(huán)代碼被優(yōu)化。

支持posix信號量的情況下,如果spin幾次都沒有加鎖成功則使用信號量進(jìn)行排隊(duì),wait表示排隊(duì)的個(gè)數(shù)。
循環(huán)調(diào)用sem_wait(&mtx->sem) 將進(jìn)程阻塞等待其它進(jìn)程釋放鎖,其它進(jìn)程釋放鎖時(shí)會(huì)檢查是否有進(jìn)程進(jìn)行wait,如果有則通過信號量wakeup正在wait的一個(gè)進(jìn)程,用信號量避免一直占用CPU。
解鎖:

解鎖就是將lock賦值為0。然后會(huì)調(diào)用ngx_shmtx_wakeup(mtx)函數(shù)。

wakeup函數(shù)檢查當(dāng)前有沒有進(jìn)程在排隊(duì),如果有排隊(duì)的進(jìn)程,則通過sem_post(&mtx->sem)激活一個(gè)排隊(duì)的進(jìn)程。
這個(gè)for循環(huán)是一個(gè)小技巧,先將wait共享變量保存在本地局部變量中,然后對wait進(jìn)行檢查,最后再通過cas修改wait共享變量,如果在這中間wait變量被其它進(jìn)程修改,那么本次cas就是失敗,這樣就可以保證將wait檢查和wait修改兩個(gè)步驟原子執(zhí)行,這也是樂觀鎖的實(shí)現(xiàn)。
讀寫鎖
nginx實(shí)現(xiàn)的讀寫鎖對寫鎖不太友好,可能會(huì)有寫鎖饑餓的問題。
#define NGX_RWLOCK_WLOCK ((ngx_atomic_uint_t) -1) 定義寫鎖宏
加寫鎖:

沒有鎖的時(shí)候加寫鎖。
加讀鎖:

加讀鎖的時(shí)候依然用到了上面的技巧,如果沒有加寫鎖,則加讀共享鎖。
解鎖:

先解寫鎖,再解讀鎖。
spinlock
加鎖:

spinlock加鎖居然也會(huì)調(diào)用yield,說明這個(gè)spinlock目前只能用于進(jìn)程上下文。
解鎖:
#define ngx_unlock(lock) *(lock) = 0
這個(gè)地方?jīng)]有指明內(nèi)存序會(huì)不會(huì)有問題呢?我理解其它進(jìn)程會(huì)在本進(jìn)程解鎖的一段時(shí)間之后才會(huì)發(fā)現(xiàn)這次解鎖,會(huì)不會(huì)造成饑餓?待研究。
讀寫鎖(優(yōu)先寫鎖)
加寫鎖:

如果當(dāng)前無鎖,則加寫鎖,如果當(dāng)前被上鎖,則通過信號量等待。
加讀鎖:

如果當(dāng)前不是寫鎖并且隊(duì)列中沒有等待的寫鎖,則加讀鎖,否則自旋加讀鎖。
解鎖:

解寫鎖,然后weakup等待的寫鎖。
解讀鎖,如果讀鎖全部完成,然后weakup等待的寫鎖。

weakup函數(shù)不變,如果wait不為0,則激活一個(gè)寫鎖。不知道是不是正確啊,哈哈
也看到用兩個(gè)互斥鎖實(shí)現(xiàn)讀寫鎖的例子,僅使用互斥鎖實(shí)現(xiàn)讀寫鎖,有才啊?