概念
線程的池子,有很多線程,但是數量不會超過池子的限制。(需要用到多執行流並行進行任務處理的時候,就從池子中取出)
一種線程使用模式。線程過多會帶來調度開銷,進而影響緩存局部性和整體性能。而線程池維護着多個線程,等待着監督管理者分配可併發執行的任務。這避免了在處理短時間任務時創建與銷燬線程的代價。線程池不僅能夠保證內核的充分利用,還能防止過分調度。可用線程數量應該取決於可用的併發處理器、處理器內核、內存、網絡 sockets等的數量。
爲什麼用線程池
有大量的數據處理請求,需要多執行流併發/並行處理
若是一個數據請求的到來伴隨一個線程的創建去處理,則會產生一些風險以及一些不必要的消耗:
- 線程若不限制數量的創建,在峯值壓力下,線程創建過多,資源耗盡,有程序崩潰的風險
- 處理一個任務的時間:創建線程時間t1+任務處理時間t2+線程銷燬時間t3 = T。若t2/T比例佔據不夠高,則表示大量的資源用於線程的創建與銷燬成本上,因此線程池使用已經創建好的線程進行循環任務處理,就避免了大量線程的頻繁創建於銷燬的時間成本.
應用場景
-
需要大量的線程來完成任務,且完成任務的時間比較短。 WEB服務器完成網頁請求這樣的任務,使用線程池技術是非常合適的。因爲單個任務小,而任務數量巨大,你可以想象一個熱門網站的點擊次數。 但對於長時間的任務,比如一個Telnet連接請求,線程池的優點就不明顯了。因爲Telnet會話時間比線程的創建時間大多了。
-
對性能要求苛刻的應用,比如要求服務器迅速響應客戶請求。
-
接受突發性的大量請求,但不至於使服務器因此產生大量線程的應用。突發性大量客戶請求,在沒有線程池情況下,將產生大量線程,雖然理論上大部分操作系統線程數目最大值不是問題,短時間內產生大量線程可能使內存到達極限,出現錯誤.
實現一個線程池
大量線程(每個線程中都是進行循環的任務處理)+ 任務緩衝隊列
- 線程的入口函數,都是在創建線程的時候,就固定傳入的,導致線程池中的線程進行任務處理的方式過於單一
- 因爲線程的入口函數都是一樣的,處理流程也就都是一樣的,只能處理單一方式的請求。 - 靈活性太差
若任務隊列中的任務,不僅僅是單純的數據,而是包含任務處理方式在內的數據,這時候,線程池中的線程只需要使用傳入的方式,處理傳入的數據即可,不需要關心是什麼數據,如何處理。 - 提高線程池的靈活性
typedef void (*_handler)(int data);
class MyTask{
private:
int _data; // 要處理的數據
handle_t handler; // 處理數據的方法
public:
SetTask(int data, handler_t handler); // 讓用戶自己傳入要處理的數據和方法,組織出一個任務節點
Run(){
return _handle(_data);
}
};
每個線程的入口函數中,只需要不斷的獲取任務節點,調用任務節點中的Run接口就可以實現任務的處理。
#include <iostream>
#include <cstdio>
#include <queue>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define MAX_THREAD 5
typedef void (*handler_t)(int);
class ThreadTask{
public:
ThreadTask():_data(-1), _handler(NULL)
{
}
void SetTask(int data, handler_t handler){
_data = data;
_handler = handler;
}
void Run(){ // 外部只需要調用Run,不需要關心任務如何處理
return _handler(_data);
}
private:
int _data;
handler_t _handler;
};
class ThreadPool{
public:
ThreadPool(int max_thr = MAX_THREAD):
_thr_max(max_thr)
{
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_cond, NULL);
for(int i = 0; i < _thr_max; i++){
pthread_t tid;
int ret = pthread_create(&tid, NULL, thr_start, this);
if(ret != 0){
printf("thread create error\n");
exit(-1);
}
}
}
~ThreadPool(){
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
bool TaskPush(ThreadTask &task){
pthread_mutex_lock(&_mutex);
_queue.push(task);
pthread_mutex_unlock(&_mutex);
pthread_cond_broadcast(&_cond); // 入隊後喚醒所有線程,誰搶到誰處理
return true;
}
// 類的成員函數,有一個隱藏的默認參數(this指針)
// 線程的入口函數只有一個參數,所以設置爲static
static void *thr_start(void * arg){
ThreadPool *p = (ThreadPool *) arg;
// 不斷的從任務隊列中取出任務,執行任務的Run接口就可以
while(1){
pthread_mutex_lock(&p->_mutex);
while(p->_queue.empty()){
pthread_cond_wait(&p->_cond, &p->_mutex);
}
ThreadTask task;
task = p->_queue.front();
p->_queue.pop();
pthread_mutex_unlock(&p->_mutex);
task.Run(); // 任務的處理要放在解鎖之外,因爲當前的鎖保護的是隊列的操作
}
return NULL;
}
private:
int _thr_max; // 線程池中線程的最大數量 - 根據這個初始化創建指定數量的線程
std::queue<ThreadTask> _queue;
pthread_mutex_t _mutex; // 保護隊列操作的互斥鎖
pthread_cond_t _cond; // 實現從隊列中獲取節點的同步條件變量
};
void test_func(int data){
int sec = (data % 3) + 1;
printf("tid:%p -- get data:%d , sleep:%d\n", pthread_self(), data, sec);
sleep(sec);
}
void tmp_func(int data){
printf("tif:%p -- tmp_func\n", pthread_self());
sleep(1);
}
int main(){
ThreadPool pool;
for(int i = 0; i < 10; i++){
ThreadTask task;
if(i % 2 == 0)
task.SetTask(i, test_func);
else
task.SetTask(i, tmp_func);
pool.TaskPush(task);
}
sleep(1000);
return 0;
}
一次運行結果:
如果本篇博文有幫助到您,留個贊激勵一下博主吶~~