Linux程式設計之執行緒池的設計與實現(C 98)

2022-02-22 16:09:00 字數 3533 閱讀 4840

假設伺服器的硬體資源「充裕」,那麼提高伺服器效能的乙個很直接的方法就是空間換時間,即「浪費」伺服器的硬體資源,以換取其執行效率。提公升伺服器效能的乙個重要方法就是採用「池」的思路,即對一組資源在伺服器啟動之初就被完全建立好並初始化,這稱為靜態資源分配。當伺服器進入正式執行階段,即開始處理客戶端請求時,如果它需要相關資源就可以直接從池中獲取,無需動態分配。很顯然,直接從池中取得所需要資源比動態分配資源的速度快得多,因為分配系統資源的系統呼叫都是很耗時的。當伺服器處理完乙個客戶端連線後,可以把相關資源放回池中,無須執行系統呼叫釋放資源。從最終效果來看,資源分配和**的系統呼叫只發生在伺服器的啟動和結束,這種「池」的方式避免了中間的任務處理過程對核心的頻繁訪問,提高了伺服器的效能。我們常用的執行緒池和記憶體池都是基於以上「池」的優勢所設計出來的提公升伺服器效能的方法,今天打算以c++98設計乙個基於linux系統的簡單執行緒池。

首先想一想,我們一般的伺服器都是動態建立子執行緒來實現併發伺服器的,比如每當有乙個客戶端請求建立連線時我們就動態呼叫pthread_create去建立執行緒去處理該連線請求。這種模式有什麼缺點呢?

所以我們為了進一步提公升伺服器效能,可以採取「池」的思路,把執行緒的建立放在程式的初始化階段一次完成,這就避免了動態建立執行緒導致伺服器響應請求的效能下降。

以單例模式設計執行緒池,保證執行緒池全劇唯一;

在獲取執行緒池例項進行執行緒池初始化:執行緒預先建立+任務佇列建立;

建立乙個任務類,我們真實的任務會繼承該類,完成任務執行。

根據以上思路我們可以給出這麼乙個執行緒池類的框架:

class threadpool

;

下面開始講解一些實現細節。

首先我們以餓漢單例模式來設計這個執行緒池,以保證該執行緒池全域性唯一:

建構函式私有化

提供乙個靜態函式來獲取執行緒池物件

//餓漢模式,執行緒安全

threadpool* threadpool::createthreadpool(int num)

threadpool* pmypool = threadpool::createthreadpool(5);

執行緒池物件初始化時我們需要做三件事:相關變數的初始化(執行緒池狀態、互斥鎖、條件變數等)+任務佇列的建立+執行緒預先建立

threadpool::threadpool(int num):threadsnum(num)

執行緒池的數目根據物件建立時輸入的數目來建立,如果不指定數目,我們就是使用預設數目10個。

void threadpool::createthreads()

void setarg(void* a)

virtual int run()=0;

protected:

void* arg;

};

typedef struct

msg_t;

class mytask: public task

};

真正使用該類時就自己定義乙個子類繼承task類,並實現run()函式,並通過setarg()方法去設定傳入的引數。比如可以這麼用:

msg_t msg[10];

mytask task_a[10];

//模擬生產者生產任務

for(int i=0;i<10;i++)

現在來到執行緒池設計中最難搞的地方:執行緒排程。乙個任務來了,究竟怎麼讓空閒執行緒去拿任務去做呢?我們又如何保證空閒的執行緒不斷地去拿任務呢?

抽象而言,這是乙個生產者消費者的模型,系統不斷往任務佇列裡送任務,我們通過互斥鎖和條件變數來控制任務的加入和獲取,執行緒每當空閒時就會去呼叫taketask()去拿任務。如果佇列沒任務那麼一些沒獲得互斥鎖的執行緒就會擁塞等待(因為沒鎖),獲得互斥鎖的那個執行緒會因為沒任務而擁塞等待。一旦有任務就會喚醒這個帶鎖線程拿走任務釋放互斥鎖。看看**層面是如何操作的:

加入乙個任務

void threadpool::addtask(task* ptask)

取走乙個任務

task* threadpool::taketask()

if(!isrunning)

else if(taskqueue.empty())

ptask = taskqueue.front();

taskqueue.pop();

pthread_mutex_unlock(&mutex);

}return ptask;

}

執行緒中的**函式。這裡注意的是,如果取到的任務為空,我們認為是執行緒池關閉的訊號(執行緒池銷毀時我們會在析構函式中呼叫pthread_cond_broadcast(&condition)來通知執行緒來拿任務,拿到的當然是空指標),我們退出該執行緒。

void* threadpool::threadfunc(void* arg)

printf("take one...\n");

task->run();}}

下面給出乙個執行緒池的乙個使用例子。可以看出,我首先定義了msg_t的結構體,這是因為我們的服務響應函式是帶引數的,所以我們定義了這個結構體並把其位址作為引數傳進執行緒池中去(通過setarg方法)。然後我們也定義了乙個任務類mytask繼承於task,並重寫了run方法。我們要執行的服務函式就可以寫在run函式之中。當需要往任務佇列投放任務時呼叫addtask()就可以了,然後執行緒池會自己安排任務的分發,外界無須關心。所以乙個執行緒池執行任務的過程可以簡化為:createthreadpool() -> setarg() -> addtask -> while(1) -> delete pmypool

#include #include "thread_pool.h"

#include #include typedef struct

msg_t;

class mytask: public task

};int main()

; msg_t msg[10];

mytask task_a[10];

//模擬生產者生產任務

for(int i=0;i<10;i++)

while(1)

sleep(1);

}delete pmypool;

return 0;

}

程式具體執行的邏輯是,我們建立了乙個5個執行緒大小的執行緒池,然後我們又生成了10個任務,往任務佇列裡放。由於執行緒數小於任務數,所以當每個執行緒都拿到自己的任務時,任務佇列中還有5個任務待處理,然後有些執行緒處理完自己的任務了,又去佇列裡取任務,直到所有任務被處理完了,迴圈結束,銷毀執行緒池,退出程式。

完整的執行緒池框架和測試例子在我的github。

併發程式設計之 執行緒池

執行緒池做的工作主要是控制執行的執行緒的數量,處理過程中將任務放入佇列,然後再執行緒建立後啟動這些任務,如果執行緒數量超過了最大數量,超出數量的執行緒排隊等候,等其它執行緒執行完畢,再從佇列中取出任務來執行。他的主要特點為 執行緒復用 控制最大併發數,管理執行緒。第一 降低資源消耗。通過重複利用已建...

併發程式設計之執行緒池

步驟1 自定義拒絕策略介面 1 執行緒池狀態 threadpoolexecutor使用int的高3位來表示執行緒池狀態,低29位表示執行緒數量 從數字上比較,terminated tidying stop shutdown running 這些資訊儲存在乙個原子變數ctl中目的是將執行緒池狀態與執行...

併發程式設計之執行緒池,程序池

池 受限於硬體的發展,硬體跟不上軟體的發展 在保證計算機硬體安全的情況下,最大限度的利用了計算機 池其實是降低了程式的執行效率,但是保證了計算機硬體的安全 我們再使用程序和執行緒時,不可能無限制的去開程序或執行緒。因此我們需要用到程序池,執行緒池來解決這一問題。1.concurrent模組是用來建立...