1. 緣起 nginx
隨着SMP(Symmetrical Multi-Processing)架構的流行和epoll類系統調用對非阻塞fd監視的支持,高性能服務器端的開發已經可以實現CPU計算和IO的分離。爲了充分發揮CPU的計算能力,服務器端的設計必需要儘可能減小線程切換。引發線程切換最重要的緣由之一就是對mutex和semaphor等鎖的使用。本文從計算機體系架構、操做系統的支持和mutex的實現完全分析Linux用戶空間mutex的實現,分析的源碼版本是glib-2.3.4和kernel-2.6.8。
2.
體系結構和指令的支持
在UP(uni processor)架構下,從用戶空間的角度看,中斷打斷了程序的正常執行。操做系統在處理完中斷以後,返回用戶空間的以前,從新調度系統中的線程執行。因爲CPU是在執行彙編指令結束後響應中斷,那麼單條彙編指令的執行就是原子的。
在SMP下,因爲存在CPU Local Cache和每一個CPU的指令週期不一樣,單條彙編指令的執行不會是原子的。X86 SMP提供了一個lock指令前綴,使得某些彙編指令的執行是原子的。看以下x86_64體系結構的彙編代碼,來自glibc。
Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2中對cmpxchg指令的解釋以下:
This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.
全部以lock爲前綴的指令都起內存柵欄的做用。內存柵欄使編譯器確保對RAM中數據的改變對全部CPU都是可見的。
上述彙編對應的僞代碼:
Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2中對cmpxchg指令的解釋以下:
This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.
全部以lock爲前綴的指令都起內存柵欄的做用。內存柵欄使編譯器確保對RAM中數據的改變對全部CPU都是可見的。
上述彙編對應的僞代碼:
3.
操做系統支持
實現
wait的步驟以下:
實現
wake up的步驟以下:
A. 執行
wait的
A到
C。
C. 喚醒在鎖上的一個等待線程。
4.
pthread_mutex
實現分析
pthread_mutex_lock()實如今
glibc-2.3.4 pthread_mutex_lock.c文件的
33行,該函數會根據
mutex在
init的時候設置的屬性,選擇不一樣的執行路徑。
mutex的屬性有四種:
A.
PTHREAD_MUTEX_TIMED_NP:默認屬性。
pthread_mutex_lock()直接調用
lll_mutex_lock()。
B.
PTHREAD_MUTEX_RECURSIVE_NP:檢查
mutex owner 是否爲當前線程。該屬性容許線程屢次獲取該鎖。
C.
PTHREAD_MUTEX_ERRORCHECK_NP:若是同一線程兩次
lock,會返回錯誤。
D.
PTHREAD_MUTEX_ADAPTIVE_NP:該鎖會先
n次調用
lll_mutex_trylock(),
n爲用戶定義和
100的最小值。若是仍然失敗,則調用
lll_mutex_lock()。
lll_mutex_trylock()不會調用
futex。
5.
spin lock
實現
nginx實現了
spin lock以保護多進程對
listen port的互斥
accept。
spinlock的實現以下:
Spinlock本質上是一個「忙等」鎖,因爲其不存在下節中總結的
mutex的缺點,其對於小資源是最高效的鎖。相比上節中
mutex的
PTHREAD_MUTEX_ADAPTIVE_NP屬性,
nginx的
spinlock是一個更完美的實現方案。
6.
總結
在設置
PTHREAD_MUTEX_TIMED_NP屬性和單進程多線程模型下,
pthread_mutex_lock()對同進程的其餘線程的影響以下:
A.
pthread_mutex_lock()佔用的大部分
CPU時間當中,直接影響其餘線程調用
mmap(),
brk(),
malloc和
free()。
B. 對進程處理
page fault也會有影響。
D. 最重要的是,鎖的使用會引發線程的頻繁切換,致使
cpu cache miss和
TLB miss。
對於系統中,須要互斥訪問的資源,以下建議:
A. 內核中對於小資源如鏈表的增刪,可能是使用
spin lock保護。
B. 在設置
PTHREAD_MUTEX_ADAPTIVE_NP屬性下,
mutex既能夠是
spin lock,也能夠是阻塞鎖。
C. 使用
atomic_add_return(i, v),原子對變量
i增長
v值,而且返回操做後的值。相反操做:
atomic_sub_return(i, v)。
D. 使用
Per-CPU variables,例如多線程程序中要每隔
1秒,統計某項操做的值。該變量最好是
cache alignment。
E. 對於如數據庫頻繁更新的操做,可使用數據庫的多版本併發控制方法減小對
mutex的
lock。