為什麼需要多執行緒?
最簡單的多執行緒長啥樣?
為什麼需要執行緒池,有什麼問題?
實現的主要原理是什麼?
帶著這幾個問題,我們依次展開。
1.為什麼需要多執行緒?
大部分程式畢竟都不是計算密集型的,簡單的說,正常情況下,以單執行緒的模式來寫對程式設計師而言是最舒心的。因為所有的程式碼都是順序執行,非常容易理解!函式一級一級往下呼叫,程式碼一行一行執行。但是,程式碼的世界裡,雖然cpu還好,但是卻經常需要用到io資源,或者是其他伺服器的網路資源,比如像資料庫,如果這個時候因此把程式卡住,不管是客戶端還是客戶端都對使用者體驗相當糟糕。當然了,計算密集型的運算就更需要多執行緒,防止主執行緒被卡住。
2.最簡單的多執行緒長啥樣?
舉個最簡單的例子,伺服器採用阻塞式socket,有一個網路執行緒負責收發包(IO),然後有一個邏輯主執行緒負責相應的業務操作,主執行緒和網路執行緒之間通過最簡單的訊息佇列進行交換,而這個訊息隊例明顯是兩個執行緒都要訪問(輪詢訊息佇列是否為空)到的,所以,我們需要給這個訊息佇列上鎖(std::mutex),即可以解決問題。由於比較簡單我們就不需要看這個怎麼碼了。這種模式雖然簡單,但是在合適的崗位上,也是極好的!
3.那為什麼需要執行緒池呢,有什麼問題?
還以剛才的伺服器舉例,如果業務執行緒邏輯比較複雜,又或者他需要訪問資料庫或者是其他伺服器的資源,讀取檔案等等呢?當然他可以採用非同步的資料庫介面,但是採用非同步意味著業務程式碼被碎片化。非同步是典型的討厭他,但是又幹不掉他的樣子。離題了。迴歸。這個時候我們需要多個業務執行緒處理了。多個執行緒就意味著多一份處理能力!回到上個問題,我們的多執行緒採用輪詢訊息佇列的方式來交換資訊,那麼這麼多個執行緒,不斷的上鎖解鎖,光這個成本就夠了。這個時候,條件變數就上線了(std::condition_variable)就登場了
4.實現的主要原理是什麼?
業務執行緒不要輪詢訊息佇列了,而所有的業務執行緒處於等待狀態,當有訊息再來的時候,再由產生訊息的人,在我們示例場景就是網路執行緒了,隨便喚醒一個工人執行緒即可。看看最關鍵的程式碼
//消費者 void consumer() { //第一次上鎖 std::unique_lock < std::mutex > lck(mutex_); while (active_) { //如果是活動的,並且任務為空則一直等待 while (active_ && task_.empty()) cv_.wait(lck); //如果已經停止則退出 if(!active_) break; T *quest = task_.front(); task_.pop(); //從任務佇列取出後該解鎖(任務佇列鎖)了 lck.unlock(); //執行任務後釋放 proc_(quest); //delete quest; //在proc_已經釋放該指標了 //重新上鎖 lck.lock(); } }
算了,還是直接貼完整程式碼,看註釋吧
#ifndef _WORKER_POOL_H_ #define _WORKER_POOL_H_ //file: worker_pool.h //#define _CRT_SECURE_NO_WARNINGS // g++ -g -std=c++11 1.cc -D_GLIBCXX_USE_NANOSLEEP -lpthread */ #include <vector> #include <queue> #include <thread> #include <mutex> #include <condition_variable> //#include <chrono> template<typename T> class WorkerPool { public: typedef WorkerPool<T> THIS_TYPE; typedef std::function<void(T*)> WorkerProc; typedef std::vector< std::thread* > ThreadVec; WorkerPool() { active_ = false; } virtual ~WorkerPool() { for(ThreadVec::iterator it = all_thread_.begin();it != all_thread_.end();++it) delete *it; all_thread_.clear(); } void Start(WorkerProc f,int worker_num=1) { active_ = true; all_thread_.resize(worker_num); for (int i = 0; i < worker_num;i++ ) { all_thread_[i] = new std::thread(std::bind(&THIS_TYPE::consumer,this)); } proc_ = f; } //生產者 void Push(T *t) { std::unique_lock < std::mutex > lck(mutex_); task_.push(t); cv_.notify_one(); } void Stop() { //等待所有的任務執行完畢 mutex_.lock(); while (!task_.empty()) { mutex_.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); cv_.notify_one(); mutex_.lock(); } mutex_.unlock(); //關閉連線後,等待執行緒自動退出 active_ = false; cv_.notify_all(); for(ThreadVec::iterator it = all_thread_.begin(); it != all_thread_.end();++it) (*it)->join(); } private: //消費者 void consumer() { //第一次上鎖 std::unique_lock < std::mutex > lck(mutex_); while (active_) { //如果是活動的,並且任務為空則一直等待 while (active_ && task_.empty()) cv_.wait(lck); //如果已經停止則退出 if(!active_) break; T *quest = task_.front(); task_.pop(); //從任務佇列取出後該解鎖(任務佇列鎖)了 lck.unlock(); //執行任務後釋放 proc_(quest); //delete quest; //在proc_已經釋放該指標了 //重新上鎖 lck.lock(); } } std::mutex mutex_; std::queue<T*> task_; std::condition_variable cv_; bool active_; std::vector< std::thread* > all_thread_; WorkerProc proc_; }; #endif
寫一個類繼承一下,並寫一個工作函式和回撥函式處理
#include "worker_pool.h" #include <iostream> //為了多耗點cpu,計算斐波那契數列吧 static int fibonacci(int a) { //ASSERT(a > 0); if (a == 1 || a == 2) return 1; return fibonacci(a-1) + fibonacci(a-2); } //非同步計算任務 struct AsyncCalcQuest { AsyncCalcQuest():num(0),result(0) {} //計算需要用到的變數 int num; int result; }; //為了測試方便,引入全域性變數用於標識執行緒池已將所有計算完成 const int TOTAL_COUNT = 1000000; int now_count = 0; //繼承一下執行緒池類,在子類處理計算完成的業務,在我們這裡,只是列印一下計算結果 class CalcWorkerPool:public WorkerPool<AsyncCalcQuest> { public: CalcWorkerPool(){} virtual ~CalcWorkerPool() { } //在工人執行緒中執行 void DoWork(AsyncCalcQuest *quest) { //算了,不算這個了,根本算不出來 quest->result = fibonacci(quest->num); //quest->result = quest->num*0.618; //並將已完成任務返回到準備回撥的列表 std::unique_lock<std::mutex > lck(mutex_callbacks_); callbacks_.push_back(quest); } //在主執行緒執行 void DoCallback() { //組回撥任務上鎖 std::unique_lock<std::mutex > lck(mutex_callbacks_); while (!callbacks_.empty()) { auto *quest = callbacks_.back(); {//此處為業務程式碼列印一下吧 std::cout << quest->num << " " << quest->result << std::endl; now_count ++; } delete quest; //TODO:這裡如果採用記憶體池就更好了 callbacks_.pop_back(); } } private: //這裡是準備給回撥的任務列表 std::vector<AsyncCalcQuest*> callbacks_; std::mutex mutex_callbacks_; }; int main() { CalcWorkerPool workers; //工廠開工了 8個工人喔 workers.Start(std::bind(&CalcWorkerPool::DoWork,&workers,std::placeholders::_1),8); //開始產生任務了 for (int i=0; i<TOTAL_COUNT; i++) { AsyncCalcQuest *quest = new AsyncCalcQuest; quest->num = i%40+1; workers.Push(quest); } while (now_count != TOTAL_COUNT) { workers.DoCallback(); } workers.Stop(); return 0; }
linux完整專案 https://github.com/linbc/worker_pool.git