摘要:在各種業務解決方案的設計中,伺服器處理任務的效率是衡量方案優劣的乙個重要標準。使用多執行緒技術併發處理任務是提高伺服器效率的乙個主要手段。但是頻繁的執行緒建立、銷毀和任務的分配也會降低系統效率。本文設計了乙個通用的執行緒池,根據不同伺服器所處理的任務的特點,可以設定對應的執行緒池引數,最大幅度的提高系統效能。
關鍵字:執行緒池多執行緒任務虛函式異常
概述在各種業務解決方案的設計過程中,伺服器處理任務的效率往往決定了方案的成敗。多執行緒處理任務是提高伺服器效率的主要手段,它提高了對伺服器資源的利用,使得任務可以併發處理。但如果伺服器處理的任務的特點是輕量級、頻率高,那麼執行緒的建立與銷毀會非常頻繁,而系統用於處理執行緒的建立與銷毀的開銷會佔相當大的比重,反而降低了系統的效率。通過執行緒池技術,可以減少頻繁的執行緒的建立與銷毀對系統效能的影響。
執行緒池是預先建立執行緒的一種技術。執行緒池在還沒有任務到來之前,建立一定數量(n1)的執行緒,放入空閒佇列中。這些執行緒都是處於阻塞(suspended)狀態,不消耗cpu,但占用較小的記憶體空間。當任務到來後,緩衝池選擇乙個空閒執行緒,把任務傳入此執行緒中執行。當n1個執行緒都在處理任務後,緩衝池自動建立一定數量的新執行緒,用於處理更多的任務。當系統比較空閒時,大部分執行緒都一直處於暫停狀態,執行緒池自動銷毀一部分執行緒,**系統資源。
通用執行緒緩衝池的設計,不僅要實現上述功能,還要考慮此設計的可移植性,減少重複開發。設計中需要考慮的重點是:
任務物件的通用性;
執行緒建立和銷毀策略;
任務的分配策略。
分析與設計
1、任務物件的通用性
不同的業務解決方案有各自獨特的任務處理方法,任務的劃分上也就千差萬別。為了使得在處理任務物件的時候達到一定程度的通用性,任務物件的設計上必須與實際任務的處理邏輯完全無關。從任務執行的角度看,任務不過是處理流程的一次或者多次執行的過程,可以這樣來定義任務介面:
class task
public:
task();
virtual ~task();
virtual bool run() = 0;
task類是所有任務類的基類,其中的純虛函式run()是任務流程的入口,工作執行緒在處理任務的時候就從此處開始執行任務的處理流程。設計乙個新的任務時,只需要繼承task介面,新的任務就可以放入執行緒池中執行。
任務的建立、執行和銷毀這樣來設計:
(1)任務在其需要的時候才建立。任務的建立通過new操作,動態建立具體的任務物件,然後傳入執行緒池,由執行緒池自動分配執行緒來執行此任務。
(2)任務是否執行完畢由其自身來決定。乙個未知任務什麼時候執行完畢是不可能**的,必須任務本身來決定。這個策略通過,task::run()的返回值來實現。當工作執行緒執行一次任務時,如果返回值為true,表示任務執行完畢,就用delete操作銷毀此任務;如果返回值為false,表示任務需要執行的工作並未完成,繼續執行此任務。
2、執行緒的建立與銷毀
執行緒緩衝池中的維持的執行緒數量應該按照任務處理的需求來定。
在緩衝池剛剛建立時,執行緒池中有一定數量(n1)的已建立好的執行緒,這樣可以使得新任務可以及時的得到執行。比如,某客戶端在向伺服器傳送登陸請求的時候,這樣乙個請求使得伺服器通常需要建立好幾個相互有關聯的任務。也就是說,客戶端與伺服器端的一次互動,通常會產生一定數量的任務。根據乙個伺服器所處理的業務,估計出平均情況下,一次業務產生的任務數量n2。那麼n1應該是n2的整數倍,n1=n2×n1,減少由於執行緒不夠而再建立執行緒的概率,才能使得伺服器在業務處理初期最為高效。
當伺服器業務減少,出現大量執行緒閒置的情況,就應該銷毀一部分執行緒。很顯然,這裡應該使用超時策略,當某些執行緒在超過時間t仍然處於閒置狀態,就銷毀一部分空閒執行緒。設銷毀n4個空閒執行緒,為了減少由於執行緒不夠而再建立執行緒的概率,n4也應該是n2的整數倍,n4=n2×n3。當然,為了使得新任務及時得到處理,即使伺服器一直處於空閒,也應該保留n1個執行緒。
3、任務分配策略
在業務處理中,會有各種各樣的任務物件,這些業務物件對系統資源的使用也不同。這些任務,無論其空間複雜度如何,從執行緒執行任務這一角度來看,應該關心的主要是時間複雜度。
執行緒緩衝池在接收到新任務的時候,首先要尋找空閒執行緒,傳入新任務,然後執行任務,最後還要刪除任務,置空閒執行緒的標誌。尋找空閒執行緒、傳入任務、最後的清理工作,這些都是為了執行任務而產生的額外開銷,如果所執行的任務大多數都是輕量級任務,那麼額外開銷帶來的資源浪費就顯得很突出了。為了解決這個問題,可以給乙個執行緒傳入n5個輕量級任務,這乙個執行緒依次執行n5個輕量級任務,由於都是在很短時間內完成,並不影響任務響應的及時性。顯然,n5≥1。
實現(1) 執行緒池任務分配主迴圈(也是乙個執行緒)
這裡除了任務分配演算法外也包括了部分執行緒的建立與銷毀的演算法。
for(;;) else { // 有任務需要執行
ptask = gettask(); // 取得新任務
try {
while( !ptask->run() ) {
// 此處迴圈體為空,不斷執行直到任務執行完畢
catch( … ) {
writelog( … ); // 執行任務時產生異常,記錄入日誌
delete ptask; // 任務執行完畢,刪除此任務
在任務執行的核心部分,使用了try-catch控制塊進行異常捕獲。雖然異常會對程式速度有很略微的影響,但是因為要執行的任務是未知的,不能保證任務可以正常執行。因為乙個任務的異常而導致伺服器的服務程式崩潰,這是絕對不允許的。使用異常捕獲不僅可以保證伺服器流程的順利執行,而且把異常資訊存入日誌檔案,還可以跟蹤錯誤。
效能測試
為了檢驗此執行緒池的效能是否和預期相同,並且分析出線程池的不同引數配置對系統效能的影響,特編寫了測試程式對三組引數進行了測試,測試結果如圖1所示:
橫座標是任務數量;縱座標是消耗時間,以秒(s)為單位。
引數1:n2 = 1, n5 = 1; 引數2:n2 = 5, n5 = 1; 引數3:n2 = 5, n5 = 5
測試中,系統的總的執行緒數限制為500,任務都是5ms。這裡只針對n2和n5進行測試,n2是平均情況下系統每次向執行緒池中增加的任務數量,n5是每個執行緒一次執行任務數量。
在任務量比較小的情況下,三者的對系統效能的占用基本上相等。但是當任務量很巨大的時候,引數1比引數2效率要稍微高出一些,而引數3的執行效率幾乎是前兩者的一倍。
因為都是輕量級任務,所以n2的變化對系統效率的影響並不大,而n5的影響就很顯著。
結束語通過測試可以看出,在伺服器中使用執行緒池後,並不意味著系統效能就一定可以提公升。不同系統的任務有著各自不同的特點,這就需要根據伺服器任務的特點進一步調整緩衝池的一些關鍵引數,才能最大程度的提高系統效率。這些引數就是上面分析過程中的n1、n2、n3、n4、n5、n1、n2、n3。
利用C 語言設計可擴充套件執行緒池
摘要 在各種業務解決方案的設計中,伺服器處理任務的效率是衡量方案優劣的乙個重要標準。使用多執行緒技術併發處理任務是提高伺服器效率的乙個主要手段。但是頻繁的執行緒建立 銷毀和任務的分配也會降低系統效率。本文設計了乙個通用的執行緒池,根據不同伺服器所處理的任務的特點,可以設定對應的執行緒池引數,最大幅度...
C 語言設計可擴充套件執行緒池
在各種業務解決方案的設計過程中,伺服器處理任務的效率往往決定了方案的成敗。多執行緒處理任務是提高伺服器效率的主要手段,它提高了對伺服器資源的利用,使得任務可以併發處理。但如果伺服器處理的任務的特點是輕量級 頻率高,那麼執行緒的建立與銷毀會非常頻繁,而系統用於處理執行緒的建立與銷毀的開銷會佔相當大的比...
c語言實現執行緒池
ifndef threadpool h included define threadpool h included include typedef struct threadpool job void routine void void arg struct threadpool job next ...