網(wǎng)站開源系統(tǒng)免費網(wǎng)站建設seo
[Linux]線程池
文章目錄
- [Linux]線程池
- 線程池的概念
- 線程池的優(yōu)點
- 線程池的應用場景
- 線程池的實現(xiàn)
線程池的概念
線程池是一種線程使用模式。線程池是一種特殊的生產(chǎn)消費模型,用戶作為生產(chǎn)者,線程池作為消費者和緩沖區(qū)。
線程過多會帶來調度開銷,進而影響緩存局部和整體性能,而線程池維護著多個線程,等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務。
線程池的優(yōu)點
- 線程池避免了在處理短時間任務時創(chuàng)建與銷毀線程的代價。
- 線程池不僅能夠保證內(nèi)核充分利用,還能防止過分調度。
注意: 線程池中可用線程的數(shù)量應該取決于可用的并發(fā)處理器、處理器內(nèi)核、內(nèi)存、網(wǎng)絡sockets等的數(shù)量。
線程池的應用場景
- 需要大量的線程來完成任務,且完成任務的時間比較短。
- 對性能要求苛刻的應用,比如要求服務器迅速響應客戶請求。
- 接受突發(fā)性的大量請求,但不至于使服務器因此產(chǎn)生大量線程的應用。
線程池的實現(xiàn)
下面我們實現(xiàn)一個簡單的線程池,線程池中提供了一個任務隊列,以及若干個線程(多線程)。
- 線程池中的多個線程負責從任務隊列當中拿任務,并將拿到的任務進行處理。
- 線程池對外提供一個Push接口,用于讓外部線程能夠將任務Push到任務隊列當中。
線程池的代碼如下:
#pragma once#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <queue>const int N = 5; // 線程池內(nèi)線程數(shù)量template <class T>
class ThreadPool
{
public:ThreadPool(int num = N) : _num(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}void LockQueue(){pthread_mutex_lock(&_mutex);}void UnLockQueue(){pthread_mutex_unlock(&_mutex);}void threadWait(){pthread_cond_wait(&_cond, &_mutex);}void threadWakeUP(){pthread_cond_signal(&_cond);}T getTask(){T t = _tasks.front();_tasks.pop();return t;}bool isEmpty(){return _tasks.empty();}static void *threadRoutine(void *args){pthread_detach(pthread_self());ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);while (true){tp->LockQueue();while (tp->isEmpty()){tp->threadWait();}T t = tp->getTask();tp->UnLockQueue();t.Run();//任務處理}}void Start(){pthread_t tid;for (int i = 0; i < _num; i++){pthread_create(&tid, nullptr, threadRoutine, this);}}void PushTask(T &task) // 添加任務{LockQueue();_tasks.push(task);threadWakeUP();UnLockQueue();}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}private:int _num; // 線程數(shù)std::queue<T> _tasks; // 任務隊列pthread_mutex_t _mutex; // 保證互斥訪問任務隊列這一共享資源pthread_cond_t _cond; // 根據(jù)任務隊列中的任務數(shù)量控制線程的等待和運行
};
為什么線程池中需要有互斥鎖和條件變量?
互斥鎖: 任務隊列是一個共享資源,外部線程可以調用添加任務的接口訪問任務隊列,線程池內(nèi)部的線程可以直接訪問任務隊列處理任務,可能會造成任務隊列的并發(fā)訪問問題,因此需要利用互斥鎖保護任務隊列中的數(shù)據(jù)。
條件變量: 線程池當中的線程要從任務隊列里拿任務,前提條件是任務隊列中必須要有任務,因此線程池當中的線程在拿任務之前,需要先判斷任務隊列當中是否有任務,若此時任務隊列為空,那么該線程應該進行等待,直到任務隊列中有任務時再將其喚醒,因此我們需要引入條件變量。
當外部線程向任務隊列中Push一個任務后,此時可能有線程正處于等待狀態(tài),因此在新增任務后需要喚醒在條件變量下等待的線程。
為什么線程池中的線程執(zhí)行例程需要設置為靜態(tài)方法?
使用pthread_create
函數(shù)創(chuàng)建線程時,需要為創(chuàng)建的線程傳入一個執(zhí)行方法threadRoutine,該執(zhí)行方法只有一個參數(shù)類型為void的參數(shù),以及返回類型為void的返回值。
如果threadRoutine作為類的成員函數(shù),該函數(shù)的第一個參數(shù)是隱藏的this指針,無法通過編譯。而靜態(tài)成員函數(shù)屬于類,而不屬于某個對象,也就是說靜態(tài)成員函數(shù)是沒有隱藏的this指針的,因此我們需要將threadRoutine設置為靜態(tài)方法,此時threadRoutine函數(shù)才真正只有一個參數(shù)類型為void的參數(shù)。
但是在靜態(tài)成員函數(shù)內(nèi)部無法調用非靜態(tài)成員函數(shù),而我們需要在threadRoutine函數(shù)當中調用該類的某些非靜態(tài)成員函數(shù)。因此我們需要在創(chuàng)建線程時,向threadRoutine函數(shù)傳入的當前對象的this指針,此時我們就能夠通過該this指針在threadRoutine函數(shù)內(nèi)部調用非靜態(tài)成員函數(shù)了。
任務類型的設計
由于線程池編寫的是模板化的,因此任務類型可以是任意的,但是由于處理任務的邏輯是通過調用任務的Run函數(shù),因此任務類中必須實現(xiàn)Run函數(shù)才能使用該線程池。
例如,實現(xiàn)一個計算任務類如下:
#include <cstdlib>
#include <iostream>class Task
{
public:Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitcode(0){}void Run()//對傳入數(shù)據(jù)進行操作{switch (_op){case '+':_result = _x + _y;break;case '-':_result = _x - _y;break;case '*':_result = _x * _y;break;case '/':if (_y == 0) _exitcode = -1;else_result = _x / _y;break;case '%':if (_y == 0) _exitcode = -2;else_result = _x % _y;break;default:break;}std::string result = std::to_string(_x) + _op + std::to_string(_y) + "=" + std::to_string(_result) + "(exicode:" + std::to_string(_exitcode);std::cout << result << std::endl;}private:int _x;//左操作數(shù)int _y;//右操作數(shù)char _op;//操作符int _result;//算數(shù)結果int _exitcode;//退出碼
};
線程池內(nèi)的線程在從任務隊列拿出任務進行處理的過程,并不需要關心這些任務的類型和來源,只需要拿到任務后執(zhí)行對應的Run方法即可。
主線程實現(xiàn)
主線程只需要不斷向任務隊列當中Push任務就行了,此后線程池當中的線程會從任務隊列當中獲取到這些任務并進行處理。
#include "ThreadPoolv1.hpp"
#include "Task.hpp"
#include <memory>
#include <ctime>using namespace std;int main()
{std::unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());tp->Start();time(nullptr);const char* ops = "+-*/%";while(true){int x, y;x = rand() % 50;y = rand() % 50;char op = ops[rand()%5];Task t(x, y, op);tp->PushTask(t);sleep(1);}return 0;
}
運行代碼后會產(chǎn)生六個線程,其中一個是主線程,另外五個是線程池內(nèi)處理任務的線程: