使用c++11寫個最簡跨平臺執行緒池

冷侃發表於2015-12-12

為什麼需要多執行緒?

最簡單的多執行緒長啥樣?

為什麼需要執行緒池,有什麼問題?

實現的主要原理是什麼?

 

帶著這幾個問題,我們依次展開。

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

相關文章