筆者之前照著通用寫法練手寫過乙個小的執行緒池版本,最近幾天複習了一下,發現大多數執行緒池實現都離不開鎖的使用,如互斥量pthread_mutex*結合條件變數pthread_cond*。眾所周知,鎖的使用對於程式效能影響較大,雖然現有的pthread_mutex*在鎖的申請與釋放方面做了較大的優化,但仔細想想,執行緒池的實現是可以做到無鎖化的,於是有了本文。
如上圖所示,工作佇列由主線程和工作者執行緒共享,主線程將任務放進工作佇列,工作者執行緒從工作佇列中取出任務執行。共享工作佇列的操作需在互斥量的保護下安全進行,主線程將任務放進工作佇列時若檢測到當前待執行的工作數目小於工作者執行緒總數,則需使用條件變數喚醒可能處於等待狀態的工作者執行緒。當然,還有其他地方可能也會使用到互斥量和條件變數,不再贅述。
為解決無鎖化的問題,需要避免共享資源的競爭,因此將共享工作佇列加以拆分成每工作執行緒乙個工作佇列的方式。對於主線程放入工作和工作執行緒取出任務的競爭問題,可以採取環形佇列的方式避免。在解決了鎖機制之後,就只剩下條件變數的問題了,條件變數本身即解決條件滿足時的執行緒通訊問題,而訊號作為一種通訊方式,可以代替之,其大體程式設計正規化為:
[cpp]
view plain
copy
sigemptyset (&oldmask);
sigemptyset (&signal_mask);
sigaddset (&signal_mask, sigusr1);
rc = pthread_sigmask(sig_block, &signal_mask, null);
if(rc != 0)
...
while
(!condition)
} rc = pthread_sigmask(sig_setmask, &oldmask, null);
if(rc != 0)
在無鎖線程池中,區別於常見執行緒池的地方主要在於訊號與條件變數、任務排程演算法、增加或減少執行緒數目後的任務遷移,另外還有一點就是環形佇列的實現參考了linux核心中的kfifo實現。
(1) 訊號與條件變數
(2) 任務排程演算法
常見執行緒池實現的任務排程主要在作業系統一級通過執行緒排程實現。考慮到負載均衡,主線程放入任務時應採取合適的任務排程演算法將任務放入對應的工作者執行緒佇列,本程式目前已實現round-robin和least-load演算法。round-robin即輪詢式地分配工作,least-load即選擇當前具有最少工作的工作者執行緒放入。
(3) 任務遷移
[cpp]
view plain
copy
do while
(!__sync_bool_compare_and_swap(&
thread
->out, tmp, tmp + 1));
if(work)
// do something
(4) 環形佇列
原始碼中環形佇列實現主要參考了linux核心中kfifo的實現,如下圖所示:
佇列長度為2的整次冪,out和in下標一直遞增至越界後迴轉,其型別為unsigned int,即out指標一直追趕in指標,out和in對映至fifo的對應下標處,其間的元素即為佇列元素。
以上主要是一些方案性的說明,至於具體細節的實現有興趣的讀者可以參考有問題歡迎隨時聯絡討論.
想要獲取最新技術文章?歡迎訂閱
高效執行緒池之無鎖化實現 Linux C
筆者之前照著通用寫法練手寫過乙個小的執行緒池版本,最近幾天複習了一下,發現大多數執行緒池實現都離不開鎖的使用,如互斥量pthread mutex 結合條件變數pthread cond 眾所周知,鎖的使用對於程式效能影響較大,雖然現有的pthread mutex 在鎖的申請與釋放方面做了較大的優化,但...
多執行緒無鎖演算法之無鎖佇列的實現
今天花了近兩個小時的時間好好的理解了一下多執行緒無鎖佇列的實現,檢視了很多資料和文獻。在我看來,實現無鎖佇列的關鍵點有二 1 各個平台的原子操作或者說cas原語 2 aba問題的理解和解決。首先來說說這個cas原語,所謂cas compare and swap 即比較並交換,在 intel 處理器中...
多執行緒無鎖演算法之無鎖佇列的實現
今天花了近兩個小時的時間好好的理解了一下多執行緒無鎖佇列的實現,檢視了很多資料和文獻。在我看來,實現無鎖佇列的關鍵點有二 1 各個平台的原子操作或者說cas原語 2 aba問題的理解和解決。首先來說說這個cas原語,所謂cas compare and swap 即比較並交換,在 intel 處理器中...