1.執行緒池
1.1 執行緒池是什麼?
一種執行緒管理方式。
1.2 為什麼用執行緒池?
執行緒的建立和銷燬都需要消耗系統開銷,當執行緒數量過多,系統開銷過大,就會影響快取區域性性和整體效能。而執行緒池能夠在充分利用核心資源的前提下,避免系統資源被過度呼叫。
1.3 如何設計執行緒池?
簡單來說,線上程池中提前建立好多個執行緒,使用時從執行緒池中取出,使用完放回執行緒池。執行緒池中的執行緒排程由執行緒池中的管理者執行緒排程。
2.基於C++11的實現
Talk is cheap. Show me the code.
直接看程式,原理、函式在後面再介紹。
2.1 程式
程式主要分為四個檔案,分別為:
- Task.h //任務類
- ThreadPool.h //執行緒池類
- ThreadPool.cpp //執行緒池類實現
- main.cpp //測試程式
2.1.2 任務類Task.h
#pragma once
using callback = void(*)(void*);//函式指標,定義別名
class Task{
public:
callback func;//回撥任務函式
void* arg; //函式引數
public:
Task() { //無參建構函式
this->func = nullptr;
this->arg = nullptr;
}
Task(callback func, void* arg) {//含參建構函式
this->func = func;
this->arg = arg;
}
~Task() = default; //解構函式
Task(const Task &t) = default; //拷貝建構函式
Task& operator=(const Task &t); //拷貝賦值操作符
Task(Task &&t) = default; //移動建構函式,注意不能有const
Task& operator=(const Task &&t);//移動賦值操作符
};
2.1.2 執行緒池類ThreadPool.h
#pragma once
#include "Task.h"
#include <thread>
#include <queue>
#include <vector>
#include <atomic>
#include <mutex>
#include <condition_variable>
using namespace std;
class ThreadPool {
public:
ThreadPool(int minSize, int maxSize);//建構函式
void AddTask(Task task); //新增新任務
int GetBusyNum(); //獲取當前工作中的執行緒數
int GetAliveNum(); //獲取當前活著的執行緒數
int GetTaskQueueSize(); //獲取當前任務佇列長度
~ThreadPool();
ThreadPool(const ThreadPool &t) = default; //拷貝建構函式
ThreadPool& operator=(const ThreadPool &t); //拷貝賦值操作符
ThreadPool(ThreadPool &&t) = default; //移動建構函式
ThreadPool& operator=(const ThreadPool &&t);//移動賦值操作符
private:
queue<Task> taskQueue; //任務佇列
thread managerID;//管理者執行緒ID
vector<thread> threadIDs;//工作中的執行緒組ID
int minNum;//最小執行緒數量(如果執行緒池中執行緒的數目過少,處理器的一些核可能就無法充分利用,浪費)
int maxNum;//最大執行緒數量(如果執行緒池中執行緒的數量過多,最終它們會競爭稀缺的處理器和記憶體資源,浪費大量的時間在上下文切換上。)
atomic_int busyNum;//工作中的執行緒數量(atomic_int保證其賦值,取值操作的原子性)
atomic_int liveNum;//活著的執行緒數量
atomic_int exitNum;//將要被銷燬的執行緒數量
mutex mutexPool;//執行緒池的鎖
condition_variable cond;//條件變數
bool shutDown;//是不是要銷燬執行緒池, 銷燬為true, 不銷燬為false
static void worker(void* arg);//工作的執行緒任務函式
static void manager(void* arg);//管理者執行緒任務函式
static const int NUMBER = 2;//管理者執行緒每次增加/銷燬的執行緒數
};
2.1.3 執行緒池類實現ThreadPool.cpp
#include "ThreadPool.h"
#include <unistd.h> //pthread_self
#include <iostream>
using namespace std;
ThreadPool::ThreadPool(int minSize, int maxSize) {
do{
minNum = minSize;
maxNum = maxSize;
busyNum = 0;
liveNum = minSize;
exitNum = 0;
shutDown = false;
//初始化管理者執行緒和工作執行緒組
managerID = thread(manager, this);
threadIDs.resize(maxSize);
for(int i = 0; i < minSize; ++i) {
threadIDs[i] = thread(worker, this);
}
return;
} while(0);//do{...}while(0)結構提高程式碼健壯性
}
ThreadPool::~ThreadPool() {
shutDown = true;
if(managerID.joinable()) {//阻塞在管理者執行緒,直到其執行完,再向下進行
managerID.join();
}
cond.notify_all();//喚醒所有等待的執行緒
for(int i = 0; i < maxNum; ++i) {//依次執行工作者的執行緒
if(threadIDs[i].joinable()) {
threadIDs[i].join();
}
}
}
//新增新任務
void ThreadPool::AddTask(Task task) {
unique_lock<mutex> poolLock(mutexPool);
if(shutDown) {
return;
}
taskQueue.emplace(task);
cond.notify_all();
}
int ThreadPool::GetBusyNum() {
return busyNum;
}
int ThreadPool::GetAliveNum() {
return liveNum;
}
int ThreadPool::GetTaskQueueSize() {
unique_lock<mutex> poolLock(mutexPool);
int queueSize = taskQueue.size();
poolLock.unlock();
return queueSize;
}
//工作者執行緒
void ThreadPool::worker(void* arg) {
ThreadPool* pool = static_cast<ThreadPool*>(arg);
while(true) {
unique_lock<mutex> poolLock(pool->mutexPool);
//若當前任務佇列為空且執行緒池處於開啟狀態
while(pool->taskQueue.empty() && !pool->shutDown) {
pool->cond.wait(poolLock);//阻塞工作執行緒
//若存在待銷燬執行緒
if(pool->exitNum > 0) {
--pool->exitNum;
if(pool->liveNum > pool->minNum) {//若活著的執行緒數大於最小執行緒數,則可以進行銷燬
--pool->liveNum;
cout << "threadID: " << pthread_self() << " has exited." << endl;
return;
}
}
}
//判斷執行緒池是否關閉了
if(pool->shutDown) {
cout << "threadID: " << pthread_self() << " has exited." << endl;
return;
}
//從任務佇列中取出一個任務
Task task = pool->taskQueue.front();
pool->taskQueue.pop();
++pool->busyNum;
//解鎖
poolLock.unlock();
//執行任務
cout << "threadID: " << pthread_self() << " start to work." << endl;
task.func(task.arg);
task.arg = nullptr;
//執行完後,工作執行緒數-1
cout << "threadID: " << pthread_self() << " stop working." << endl;
--pool->busyNum;
}
}
//管理者執行緒
void ThreadPool::manager(void* arg) {
ThreadPool* pool = static_cast<ThreadPool*>(arg);
while(!pool->shutDown) {
//每隔3秒檢測一次
sleep(3);
//新增新執行緒
//若任務個數大於活著的執行緒數,且活著的執行緒數小於最大執行緒數
if(pool->GetTaskQueueSize() > pool->liveNum && pool->liveNum < pool->maxNum) {
unique_lock<mutex> poolLock(pool->mutexPool);
poolLock.lock();
int count = 0;
for(int i = 0; i < pool->maxNum && count < ThreadPool::NUMBER && pool->liveNum < pool->maxNum; ++i) {
if(pool->threadIDs[i].get_id() == thread::id()) {
cout << "Create a new thread." << endl;
pool->threadIDs[i] = thread(worker, pool);
++count;
++pool->liveNum;
}
}
poolLock.unlock();
}
//銷燬執行緒
//若忙的執行緒*2小於存活的執行緒數,且存活的執行緒數大於最小的執行緒數
if(pool->busyNum * 2 < pool->liveNum && pool->liveNum > pool->minNum) {
pool->exitNum = ThreadPool::NUMBER;
for(int i = 0; i < ThreadPool::NUMBER; ++i) {//讓工作的執行緒自殺
pool->cond.notify_all();
}
}
}
}
2.2 測試方法:
將上述檔案放在Linux下的一個資料夾(我這裡是\Share\study_threadPool\myself)
- 進入該資料夾:
cd /share/study_threadPool/myself/
- 編譯:
g++ main.cpp ThreadPool.cpp -o ThreadPool.o -pthread
- 執行:
./ThreadPool.o
2.2 C++11相關函式
- thread類
- ThreadPool.cpp第17行:
managerID = thread(manager, this);
表示建立一個新執行緒,manager
是該執行緒執行的函式,this
是該執行緒執行函式的引數。 - ThreadPool.cpp第29行:
managerID.joinable()
判斷該執行緒是否可以join - ThreadPool.cpp第30行:
managerID.join()
阻塞在該執行緒,直到其執行完 - ThreadPool.cpp第123行:
pool->threadIDs[i].get_id()
表示獲取該執行緒的ID
- mutex
- ThreadPool.cpp第42行:
unique_lock<mutex> poolLock(mutexPool);
自動加鎖與解鎖 - ThreadPool.cpp第61行:
poolLock.unlock();
解鎖 - ThreadPool.cpp第120行:
poolLock.lock();
加鎖
- condition_variable
- ThreadPool.cpp第32行:
nd.notify_all();
喚醒所有等待的執行緒
- atomic
- ThreadPool.h第34行:
atomic_int busyNum;
本質還是int,只是每次對其操作時,都能保證是原子操作
- using
- Task.h第2行:
sing callback = void(*)(void*);
函式的別名
3.除錯過程中出現的問題及解決方法
3.1 warning:#pragma once in main file
解決方案:g++編譯時不要編譯標頭檔案
3.2 移動建構函式出錯
解決方案:移動建構函式的引數不能加const