C++11線程池及其三種優(yōu)化-創(chuàng)新互聯(lián)

文章目錄
    • 1. C++11 簡單線程池
    • 2. 優(yōu)化1 - 支持任意類型任務(wù)
    • 3. 優(yōu)化2 - 支持可變參數(shù)
    • 4. 優(yōu)化3 - 通過future獲取任務(wù)函數(shù)的返回值
    • 5. 總結(jié)

?在看這篇文章之前,請先確保大致了解過線程池的基本實(shí)現(xiàn)及其原理。如果不太了解,可以先去閱讀一下這篇文章: pthread線程池的實(shí)現(xiàn)

專注于為中小企業(yè)提供成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站建設(shè)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)密山免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了上千家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。1. C++11 簡單線程池

?下面的線程池是基于C++11的線程池。編寫的思路跟前文的pthread實(shí)現(xiàn)的線程池基本一致。

#include#include#include#include#include#include#include   

templateclass ThreadPool
{public:
	ThreadPool(size_t thread_nums = std::thread::hardware_concurrency())
		:_thread_nums(thread_nums)
		, _stop(false)
	{for (size_t i = 0; i< _thread_nums; ++i)
		{	_vt.emplace_back(std::thread(&ThreadPool::LoopWork, this));//將this指針也傳過去
		}
	}

	//禁用拷貝構(gòu)造和operator=  
	ThreadPool(const ThreadPool &) = delete;
	ThreadPool& operator=(const ThreadPool &) = delete;

private:
	//線程的執(zhí)行函數(shù)  
	void LoopWork()
	{std::unique_lockul(_mtx);
		for (;;)
		{	while (_taskQueue.empty() && !_stop)
			{		_isEmpty.wait(ul);
			}

			//線程從wait中出來有2種情況: 1.有任務(wù)了 2.線程池stop為true
			if (_taskQueue.empty()) {		ul.unlock();	//退出前要先解鎖
				break;
			}
			else {		T* task = std::move(_taskQueue.front());
				_taskQueue.pop();
				ul.unlock();
				(*task)();	//任務(wù)類需要重載operator()()
				ul.lock();
			}
		}
	}

public:
	void PushTask(T& task)
	{//通過{}控制lock_guard的作用域和生命周期
		{	std::lock_guardlg(_mtx);
			_taskQueue.push(&task); //任務(wù)隊(duì)列是臨界資源(其他地方會修改)  
		}
		_isEmpty.notify_one(); //條件變量的通知并不會因?yàn)槎嗑€程而影響結(jié)果(因此可以不加鎖)
	}


	~ThreadPool()
	{_stop = true;
		_isEmpty.notify_all();
		for (size_t i = 0; i< _thread_nums; ++i)
		{	if (_vt[i].joinable()) {		_vt[i].join();
			}
		}
	}

private:
	size_t _thread_nums;
	std::vector_vt;
	std::queue_taskQueue;
	std::mutex _mtx;
	std::condition_variable _isEmpty;
	std::atomic_stop;
};

struct Task {Task(int x, int y)
		:_x(x),
		 _y(y)
	{}

	void operator()() {std::cout<< _x<< " + "<< _y<< " = "<< _x + _y<< std::endl;
	}

	int _x;
	int _y;
};

int main()
{std::shared_ptr>tp(new ThreadPool());
	Task t(1, 2);
	tp->PushTask(t);
}

輸出結(jié)果:

1 + 2 = 3

?

?

2. 優(yōu)化1 - 支持任意類型任務(wù)

?在前文的pthread線程池以及根據(jù)pthread改寫的C++11線程池都是基于模板實(shí)現(xiàn)的。因?yàn)榫€程池需要能夠接收不同類型的任務(wù)。但是將整個ThreadPool類設(shè)為模板其實(shí)不是最優(yōu)解,因?yàn)楫?dāng)ThreadPool需要處理其它類型的任務(wù)時,還需要再實(shí)例化出一個新的ThreadPool類。它并沒有實(shí)現(xiàn)一個線程池實(shí)例接收多種類型任務(wù)的功能。它實(shí)現(xiàn)的是多個線程池實(shí)例接收多種類型任務(wù),每個線程池本質(zhì)是只處理一種任務(wù)。

?為了解決上述問題,我們不應(yīng)該將整個ThreadPool都設(shè)為模板類,我們的思路是讓任務(wù)隊(duì)列能夠存放不同類型的任務(wù)。這里使用到了C++11中的function包裝器,讓任務(wù)隊(duì)列里存放function類型的任務(wù)。而在PushTask()方法里,我們需要讓該函數(shù)能夠接收任意類型的任務(wù),因此需要單獨(dú)將PushTask()方法設(shè)為模板函數(shù)。

?

#include#include#include#include#include#include#include   

class ThreadPool
{public:
	ThreadPool(size_t thread_nums = std::thread::hardware_concurrency())
		:_thread_nums(thread_nums)
		, _stop(false)
	{for (size_t i = 0; i< _thread_nums; ++i)
		{	_vt.emplace_back(std::thread(&ThreadPool::LoopWork, this));//將this指針也傳過去
		}
	}

	//禁用拷貝構(gòu)造和operator=  
	ThreadPool(const ThreadPool &) = delete;
	ThreadPool& operator=(const ThreadPool &) = delete;

private:
	//線程的執(zhí)行函數(shù)  
	void LoopWork()
	{std::unique_lockul(_mtx);
		for (;;)
		{	while (_taskQueue.empty() && !_stop)
			{		_isEmpty.wait(ul);
			}

			//線程從wait中出來有2種情況: 1.有任務(wù)了 2.線程池stop為true
			if (_taskQueue.empty()) {		ul.unlock();	//退出前要先解鎖
				break;
			}
			else {		auto task = std::move(_taskQueue.front());
				_taskQueue.pop();
				ul.unlock();
				task();
				ul.lock();
			}
		}
	}

public:
	templatevoid PushTask(F&& task)
	{{	std::lock_guardlg(_mtx);
			_taskQueue.push(std::forward(task)); //任務(wù)隊(duì)列是臨界資源(其他地方會修改)  
		}
		_isEmpty.notify_one(); //條件變量的通知并不會因?yàn)槎嗑€程而影響結(jié)果(因此可以不加鎖)
	}


	~ThreadPool()
	{_stop = true;
		_isEmpty.notify_all();
		for (size_t i = 0; i< _thread_nums; ++i)
		{	if (_vt[i].joinable()) {		_vt[i].join();
			}
		}
	}

private:
	size_t _thread_nums;
	std::vector_vt;
	std::queue>_taskQueue;//每個任務(wù)必須保證是: void返回值、無參
	std::mutex _mtx;
	std::condition_variable _isEmpty;
	std::atomic_stop;
};

?

? 優(yōu)化1 - 測試

std::mutex gb_mtx;
void Add(int x, int y)
{std::lock_guardlg(gb_mtx);	//為了保證輸出結(jié)果不會打印錯亂
	std::cout<< x<< " + "<< y<< " = "<< x + y<< std::endl;
}

int main()
{std::shared_ptrtp(new ThreadPool());
	for (int i = 0; i< 10; ++i)
	{auto f = bind(Add, i, i + 1);	//我們需要手動綁定一下函數(shù)參數(shù)
		tp->PushTask(f);
	}
	return 0;
}

輸出結(jié)果:

0 + 1 = 1
1 + 2 = 3
2 + 3 = 5
3 + 4 = 7
4 + 5 = 9
5 + 6 = 11
6 + 7 = 13
7 + 8 = 15
8 + 9 = 17
9 + 10 = 19

?

?

3. 優(yōu)化2 - 支持可變參數(shù)

?前一個版本的線程池是支持任意類型的任務(wù)的,但是我們在使用線程池的時候必須要手動bind一個函數(shù)對象,然后再傳過去(這是因?yàn)槿蝿?wù)隊(duì)列要求任務(wù)必須是void返回值且無參數(shù))。為了優(yōu)化這個問題,我們可以使用可變參數(shù)模板+bind解決。下面給出PushTask()方法的優(yōu)化:(除了PushTask, 其它地方?jīng)]有任何修改)

templatevoid PushTask(F&& task, Args&&... args)
	{//將可變參數(shù)包裝起來, 包裝后func的類型滿足了"void返回值+無參"
		auto func = std::bind(std::forward(task), std::forward(args)...);
		{	std::lock_guardlg(_mtx);
			_taskQueue.push(std::forward(func)); //傳入我們包裝好的函數(shù)對象
		}
		_isEmpty.notify_one(); //條件變量的通知并不會因?yàn)槎嗑€程而影響結(jié)果(因此可以不加鎖)
	}

? 優(yōu)化2 - 測試

std::mutex gb_mtx;
void Add(int x, int y)
{std::lock_guardlg(gb_mtx);	//為了保證輸出結(jié)果不會打印錯亂
	std::cout<< x<< " + "<< y<< " = "<< x + y<< std::endl;
}

int main()
{std::shared_ptrtp(new ThreadPool());
	for (int i = 0; i< 10; ++i)
	{//auto f = bind(Add, i, i + 1);
		//tp->PushTask(f);
		tp->PushTask(Add, i, i + 1);	//我們不需要手動綁定了, 直接傳參即可
	}
	return 0;
}

?輸出結(jié)果與測試1的相同。

?

?

4. 優(yōu)化3 - 通過future獲取任務(wù)函數(shù)的返回值

?在優(yōu)化2的基礎(chǔ)上,為了能夠接收任務(wù)函數(shù)的返回值,并且還不能讓線程阻塞。這里需要使用線程異步。我們在PushTask()中需要返回一個future類型的對象。下面是對PushTask()方法的修改。(除了PushTask, 其它地方?jīng)]有任何修改)

注意: 記得包含一下頭文件。

templateauto PushTask(F&& task, Args&&... args) ->std::future{//將可變參數(shù)包裝起來
		auto func = std::bind(std::forward(task), std::forward(args)...);
		auto task_ptr = std::make_shared>(func); //(1)
		std::functionwrapper_func = [task_ptr] {	(*task_ptr)();
		};//(2)

		{	std::lock_guardlg(_mtx);
			_taskQueue.push(wrapper_func); 
		}

		_isEmpty.notify_one(); //條件變量的通知并不會因?yàn)槎嗑€程而影響結(jié)果(因此可以不加鎖)
		return task_ptr->get_future();//(3)
	}

?在優(yōu)化2的基礎(chǔ)上,我們從(1)開始看。我們使用make_shared構(gòu)造了一個share_ptr對象, 方便我們管理。這個智能指針的類型是package_task。詳細(xì)介紹看這里: C++11 線程異步。這個package_task中包裝的函數(shù)對象的類型為decltype(task(args...))()。這個類型的含義為: 該函數(shù)對象的返回值的類型為task(args...),函數(shù)參數(shù)為空。

?(2)是對智能指針再次進(jìn)行了一層封裝,目的為了能夠?qū)⑵渥鳛閰?shù)傳給任務(wù)隊(duì)列。實(shí)際上這里可以省略不寫的,然后更改_taskQueue.push(wrapper_func);

_taskQueue.push([task_ptr]{(*task_ptr)();
	});

?(3)返回智能指針管理的package_task的future對象。

?

? 優(yōu)化3 - 測試

int Add(int x, int y)
{return x + y;
}

int main()
{std::shared_ptrtp(new ThreadPool());
	std::vector>res;	//future保存的就是函數(shù)的返回值
	for (int i = 0; i< 10; ++i)
	{res.push_back(tp->PushTask(Add, i, i + 1));
	}

	for (auto& e : res)
	{std::cout<< e.get()<< std::endl;
	}
	return 0;
}

輸出結(jié)果:

1
3
5
7
9
11
13
15
17
19

?

?

5. 總結(jié)

?我們實(shí)現(xiàn)的pthread的線程池是基于模板的,因此當(dāng)有不同類型的任務(wù)想要添加到線程池處理時,我們就必須額外創(chuàng)建一個對應(yīng)類型的線程池對象,這個過程往往不是我們想看到的。為了解決這種問題,我們給出了三種遞進(jìn)式優(yōu)化方案,每種都是基于前面一種的基礎(chǔ)上作的進(jìn)一步優(yōu)化。

?優(yōu)化1: 支持任意類型的任務(wù)函數(shù)。它的本質(zhì)是讓任務(wù)隊(duì)列能夠接收任意類型的任務(wù)函數(shù),我們這里使用了function作為任務(wù)隊(duì)列的類型,也就是說我們在PushTask()時需要事前包裝好任務(wù)函數(shù),保證其類型為void()。(返回值為void, 參數(shù)為空)

?優(yōu)化2: 支持可變參數(shù)。這是通過可變參數(shù)模板+bind實(shí)現(xiàn)的。我們允許使用者直接傳任務(wù)函數(shù)以及它的任意個參數(shù)。我們需要在push任務(wù)前包裝好任務(wù)函數(shù),保證任務(wù)函數(shù)為void()類型。而如何包裝? 這里就需要使用bind進(jìn)行包裝了。
auto func = std::bind(std::forward(task), std::forward(args)...);

?優(yōu)化3: 通過future獲取任務(wù)函數(shù)的返回值。這是通過線程異步實(shí)現(xiàn)的。獲取子線程執(zhí)行完畢的函數(shù)的返回值的方法就是使用異步。因此我們將func任務(wù)函數(shù) 包裝到了package_task任務(wù)包當(dāng)中,package_task中保存了future對象。這里我們使用了智能指針去管理package_task。由于我們最終的目的是需要將一個類型為void()的任務(wù)添加到任務(wù)隊(duì)列。因此這里我們使用了lambda表達(dá)式對智能指針再次進(jìn)行了一層封裝,里面包裝了智能指針去調(diào)用任務(wù)函數(shù)的過程。lambada表達(dá)式的類型就是void(),因此我們可以將其添加到任務(wù)隊(duì)列當(dāng)中。后續(xù)取出任務(wù)后,直接像調(diào)用函數(shù)一樣執(zhí)行任務(wù)即可。(因?yàn)槿蝿?wù)隊(duì)列當(dāng)中存的都是一個個的function函數(shù)對象, 執(zhí)行函數(shù)對象的方法就是使用operator())

雜談:

?個人覺得能夠掌握前兩種優(yōu)化就夠了。第三種相對比較難懂一些,并且沒有太大的必要。我們之所以會實(shí)現(xiàn)第三種是因?yàn)橄胍@取任務(wù)函數(shù)的返回值! 因?yàn)槲覀冊?code>優(yōu)化2的狀態(tài)下也能夠獲取到任務(wù)的返回值。我們可以傳入一個輸出型參數(shù)即可。示例如下:

void Add(int x, int y, int& res)
{res = x + y;
}

int main()
{std::shared_ptr>tp(new ThreadPool());
	int res;
	tp->PushTask(Add, 1, 2, std::ref(res));	//注意, 這里必須要使用``ref()``引用傳參!!!
	std::cout<< res<< std::endl;
	return 0;
}

輸出結(jié)果: 3

?

注: 還有很多人會將taskQueue任務(wù)隊(duì)列封裝成一個類*(SafeQueue)*,然后封裝任務(wù)隊(duì)列的各種接口,將各種加鎖、解鎖操作都封裝到了該類里面。外界在使用該類時,不需要再額外考慮鎖的問題了。

?

參考:

Github: mtrebi/thread-pool: Thread pool implementation using c++11 threads

https://zhuanlan.zhihu.com/p/367309864

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧

文章標(biāo)題:C++11線程池及其三種優(yōu)化-創(chuàng)新互聯(lián)
網(wǎng)站鏈接:http://bm7419.com/article22/dihijc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護(hù)、云服務(wù)器動態(tài)網(wǎng)站、營銷型網(wǎng)站建設(shè)、外貿(mào)建站手機(jī)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)