高效線程池之無鎖化實現(Linux C)


from:http://blog.csdn.net/xhjcehust/article/details/45844901

筆者之前練手寫過一個小的線程池版本(已上傳至https://github.com/xhjcehust/thread-pool),最近幾天復習了一下,發現大多數線程池實現都離不開鎖的使用,如互斥量pthread_mutex*結合條件變量pthread_cond*。眾所周知,鎖的使用對於程序性能影響較大,雖然現有的pthread_mutex*在鎖的申請與釋放方面做了較大的優化,但仔細想想,線程池的實現是可以做到無鎖化的,於是有了本文。

1.常見線程池實現原理

如上圖所示,工作隊列由主線程和工作者線程共享,主線程將任務放進工作隊列,工作者線程從工作隊列中取出任務執行。共享工作隊列的操作需在互斥量的保護下安全進行,主線程將任務放進工作隊列時若檢測到當前待執行的工作數目小於工作者線程總數,則需使用條件變量喚醒可能處於等待狀態的工作者線程。當然,還有其他地方可能也會使用到互斥量和條件變量,不再贅述。

2.無鎖化線程池實現原理

為解決無鎖化的問題,需要避免共享資源的競爭,因此將共享工作隊列加以拆分成每工作線程一個工作隊列的方式。對於主線程放入工作和工作線程取出任務的競爭問題,可以采取環形隊列的方式避免。在解決了鎖機制之后,就只剩下條件變量的問題了,條件變量本身即解決條件滿足時的線程通信問題,而信號作為一種通信方式,可以代替之,其大體編程范式為:

[cpp]  view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. sigemptyset(&zeromask);  
  2. sigemptyset(&newmask);  
  3. sigaddset(&newmask, SIGXX);  
  4. sigprocmask(SIG_BLOCK, &newmask, &oldmask) ;  
  5. while (!CONDITION)  
  6.     sigsuspend(&zeromask);  
  7. sigprocmask(SIG_SETMASK, &oldmask, NULL)   

3.無鎖化線程池具體實現

在無鎖線程池中,區別於常見線程池的地方主要在於信號與條件變量、任務調度算法、增加或減少線程數目后的任務遷移,另外還有一點就是環形隊列的實現參考了Linux內核中的kfifo實現。

(1)   信號與條件變量

信號與條件變量的區別主要在於條件變量的喚醒(signal)對於接收線程而言可以忽略,而在未設置信號處理函數的情況下信號的接收會導致接收線程甚至整個程序的終止,因此需要在線程池產生線程之前指定信號處理函數,這樣新生的線程會繼承這個信號處理函數。多線程中信號的發送主要采用pthread_kill,為避免使用其他信號,本程序中使用了SIGUSR1。

(2)   任務調度算法

常見線程池實現的任務調度主要在操作系統一級通過線程調度實現。考慮到負載均衡,主線程放入任務時應采取合適的任務調度算法將任務放入對應的工作者線程隊列,本程序目前已實現Round-Robin和Least-Load算法。Round-Robin即輪詢式地分配工作,Least-Load即選擇當前具有最少工作的工作者線程放入。

(3)   任務遷移

在線程的動態增加和減少的過程中,同樣基於負載均衡的考量,涉及到現有任務的遷移問題。由於工作隊列采取環形隊列的形式,任務的取出應由工作者線程完成,主線程不能實時取出工作,故在線程的動態增加時的任務遷移尚未有比較好的解決辦法,只能通過線程增加后的任務調度算法實現。在線程的動態減少后,原先線程上未能執行完的任務由主線程再次根據任務調度算法重新分配至其他存活的工作者線程隊列中。

(4)   環形隊列

源碼中環形隊列實現主要參考了Linux內核中kfifo的實現,如下圖所示:

隊列長度為2的整次冪,out和in下標一直遞增至越界后回轉,其類型為unsigned int,即out指針一直追趕in指針,out和in映射至FiFo的對應下標處,其間的元素即為隊列元素。

以上主要是一些方案性的說明,至於具體細節的實現有興趣的讀者可以參考https://github.com/xhjcehust/LFTPool,有問題歡迎隨時聯系討論,筆者正值找工作季,求fork,求拍磚!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM