?下面的線程池是基于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ì)列是臨界資源(其他地方會(huì)修改)
}
_isEmpty.notify_one(); //條件變量的通知并不會(huì)因?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ù)。但是將整個(gè)ThreadPool
類設(shè)為模板其實(shí)不是最優(yōu)解,因?yàn)楫?dāng)ThreadPool需要處理其它類型的任務(wù)時(shí),還需要再實(shí)例化出一個(gè)新的ThreadPool類。它并沒有實(shí)現(xiàn)一個(gè)線程池實(shí)例接收多種類型任務(wù)的功能。它實(shí)現(xiàn)的是多個(gè)線程池實(shí)例接收多種類型任務(wù),每個(gè)線程池本質(zhì)是只處理一種任務(wù)。
?為了解決上述問題,我們不應(yīng)該將整個(gè)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ì)列是臨界資源(其他地方會(huì)修改)
}
_isEmpty.notify_one(); //條件變量的通知并不會(huì)因?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;//每個(gè)任務(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é)果不會(huì)打印錯(cuò)亂
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); //我們需要手動(dòng)綁定一下函數(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ù)?前一個(gè)版本的線程池是支持任意類型的任務(wù)的,但是我們?cè)谑褂镁€程池的時(shí)候必須要手動(dòng)bind一個(gè)函數(shù)對(duì)象,然后再傳過去(這是因?yàn)槿蝿?wù)隊(duì)列要求任務(wù)必須是void返回值且無參數(shù))。為了優(yōu)化這個(gè)問題,我們可以使用可變參數(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ù)對(duì)象
}
_isEmpty.notify_one(); //條件變量的通知并不會(huì)因?yàn)槎嗑€程而影響結(jié)果(因此可以不加鎖)
}
? 優(yōu)化2 - 測試
std::mutex gb_mtx;
void Add(int x, int y)
{std::lock_guardlg(gb_mtx); //為了保證輸出結(jié)果不會(huì)打印錯(cuò)亂
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); //我們不需要手動(dòng)綁定了, 直接傳參即可
}
return 0;
}
?輸出結(jié)果與測試1的相同。
?
?
4. 優(yōu)化3 - 通過future獲取任務(wù)函數(shù)的返回值?在優(yōu)化2的基礎(chǔ)上,為了能夠接收任務(wù)函數(shù)的返回值,并且還不能讓線程阻塞。這里需要使用線程異步。我們?cè)?code>PushTask()中需要返回一個(gè)future
類型的對(duì)象。下面是對(duì)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(); //條件變量的通知并不會(huì)因?yàn)槎嗑€程而影響結(jié)果(因此可以不加鎖)
return task_ptr->get_future();//(3)
}
?在優(yōu)化2的基礎(chǔ)上,我們從(1)開始看。我們使用make_shared
構(gòu)造了一個(gè)share_ptr對(duì)象, 方便我們管理。這個(gè)智能指針的類型是package_task
。詳細(xì)介紹看這里: C++11 線程異步。這個(gè)package_task
中包裝的函數(shù)對(duì)象的類型為decltype(task(args...))()
。這個(gè)類型的含義為: 該函數(shù)對(duì)象的返回值的類型為task(args...)
,函數(shù)參數(shù)為空。
?(2)是對(duì)智能指針再次進(jìn)行了一層封裝,目的為了能夠?qū)⑵渥鳛閰?shù)傳給任務(wù)隊(duì)列。實(shí)際上這里可以省略不寫的,然后更改_taskQueue.push(wrapper_func);
為
_taskQueue.push([task_ptr]{(*task_ptr)();
});
?(3)返回智能指針管理的package_task的future對(duì)象。
?
? 優(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ù)想要添加到線程池處理時(shí),我們就必須額外創(chuàng)建一個(gè)對(duì)應(yīng)類型的線程池對(duì)象,這個(gè)過程往往不是我們想看到的。為了解決這種問題,我們給出了三種遞進(jìn)式優(yōu)化方案,每種都是基于前面一種的基礎(chǔ)上作的進(jìn)一步優(yōu)化。
?優(yōu)化1: 支持任意類型的任務(wù)函數(shù)。它的本質(zhì)是讓任務(wù)隊(duì)列能夠接收任意類型的任務(wù)函數(shù),我們這里使用了function
作為任務(wù)隊(duì)列的類型,也就是說我們?cè)?code>PushTask()時(shí)需要事前包裝好任務(wù)函數(shù),保證其類型為void()
。(返回值為void, 參數(shù)為空)
?優(yōu)化2: 支持可變參數(shù)。這是通過可變參數(shù)模板+bind實(shí)現(xiàn)的。我們?cè)试S使用者直接傳任務(wù)函數(shù)以及它的任意個(gè)參數(shù)。我們需要在push任務(wù)前包裝好任務(wù)函數(shù),保證任務(wù)函數(shù)為void()
類型。而如何包裝? 這里就需要使用bind進(jìn)行包裝了。auto func = std::bind(std::forward
?優(yōu)化3: 通過future獲取任務(wù)函數(shù)的返回值。這是通過線程異步實(shí)現(xiàn)的。獲取子線程執(zhí)行完畢的函數(shù)的返回值的方法就是使用異步。因此我們將func
任務(wù)函數(shù) 包裝到了package_task
任務(wù)包當(dāng)中,package_task
中保存了future對(duì)象。這里我們使用了智能指針去管理package_task
。由于我們最終的目的是需要將一個(gè)類型為void()
的任務(wù)添加到任務(wù)隊(duì)列。因此這里我們使用了lambda表達(dá)式對(duì)智能指針再次進(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)中存的都是一個(gè)個(gè)的function
雜談:
?個(gè)人覺得能夠掌握前兩種優(yōu)化就夠了。第三種相對(duì)比較難懂一些,并且沒有太大的必要。我們之所以會(huì)實(shí)現(xiàn)第三種是因?yàn)橄胍@取任務(wù)函數(shù)的返回值! 因?yàn)槲覀冊(cè)?code>優(yōu)化2的狀態(tài)下也能夠獲取到任務(wù)的返回值。我們可以傳入一個(gè)輸出型參數(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
?
注: 還有很多人會(huì)將taskQueue
任務(wù)隊(duì)列封裝成一個(gè)類*(SafeQueue)*,然后封裝任務(wù)隊(duì)列的各種接口,將各種加鎖、解鎖操作都封裝到了該類里面。外界在使用該類時(shí),不需要再額外考慮鎖的問題了。
?
參考:
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級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧
文章標(biāo)題:C++11線程池及其三種優(yōu)化-創(chuàng)新互聯(lián)
網(wǎng)站鏈接:http://aaarwkj.com/article22/dihijc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護(hù)、云服務(wù)器、動(dòng)態(tài)網(wǎng)站、營銷型網(wǎng)站建設(shè)、外貿(mào)建站、手機(jī)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容