C++基于消息隊(duì)列的多線程實(shí)現(xiàn)示例代碼
前言
實(shí)現(xiàn)消息隊(duì)列的關(guān)鍵因素是考量不同線程訪問消息隊(duì)列的同步問題。本實(shí)現(xiàn)涉及到幾個知識點(diǎn)
std::lock_guard 介紹
std::lock_gurad 是 C++11 中定義的模板類。定義如下:
template <class Mutex> class lock_guard;
lock_guard 對象通常用于管理某個鎖(Lock)對象,因此與 Mutex RAII 相關(guān),方便線程對互斥量上鎖,即在某個 lock_guard 對象的聲明周期內(nèi),它所管理的鎖對象會一直保持上鎖狀態(tài);而 lock_guard 的生命周期結(jié)束之后,它所管理的鎖對象會被解鎖(注:類似 shared_ptr 等智能指針管理動態(tài)分配的內(nèi)存資源 )。
模板參數(shù) Mutex 代表互斥量類型,例如 std::mutex 類型,它應(yīng)該是一個基本的 BasicLockable 類型,標(biāo)準(zhǔn)庫中定義幾種基本的 BasicLockable 類型,分別 std::mutex, std::recursive_mutex, std::timed_mutex,std::recursive_timed_mutex 以及 std::unique_lock
std::unique_lock 介紹
lock_guard 最大的缺點(diǎn)也是簡單,沒有給程序員提供足夠的靈活度,因此,C++11 標(biāo)準(zhǔn)中定義了另外一個與 Mutex RAII 相關(guān)類 unique_lock,該類與 lock_guard 類相似,也很方便線程對互斥量上鎖,但它提供了更好的上鎖和解鎖控制。
顧名思義,unique_lock 對象以獨(dú)占所有權(quán)的方式( unique owership)管理 mutex 對象的上鎖和解鎖操作,所謂獨(dú)占所有權(quán),就是沒有其他的 unique_lock 對象同時擁有某個 mutex 對象的所有權(quán)。
新創(chuàng)建的 unique_lock 對象管理 Mutex 對象 m,并嘗試調(diào)用 m.lock() 對 Mutex 對象進(jìn)行上鎖,如果此時另外某個 unique_lock 對象已經(jīng)管理了該 Mutex 對象 m,則當(dāng)前線程將會被阻塞。
std::condition介紹
當(dāng) std::condition_variable 對象的某個 wait 函數(shù)被調(diào)用的時候,它使用 std::unique_lock(通過 std::mutex) 來鎖住當(dāng)前線程。當(dāng)前線程會一直被阻塞,直到另外一個線程在相同的 std::condition_variable 對象上調(diào)用了 notification 函數(shù)來喚醒當(dāng)前線程。
std::condition_variable 提供了兩種 wait() 函數(shù)。當(dāng)前線程調(diào)用 wait() 后將被阻塞(此時當(dāng)前線程應(yīng)該獲得了鎖(mutex),不妨設(shè)獲得鎖 lck),直到另外某個線程調(diào)用 notify_* 喚醒了當(dāng)前線程。
在線程被阻塞時,該函數(shù)會自動調(diào)用 lck.unlock() 釋放鎖,使得其他被阻塞在鎖競爭上的線程得以繼續(xù)執(zhí)行。另外,一旦當(dāng)前線程獲得通知(notified,通常是另外某個線程調(diào)用 notify_* 喚醒了當(dāng)前線程),wait() 函數(shù)也是自動調(diào)用 lck.lock(),使得 lck 的狀態(tài)和 wait 函數(shù)被調(diào)用時相同。
在第二種情況下(即設(shè)置了 Predicate),只有當(dāng) pred 條件為 false 時調(diào)用 wait() 才會阻塞當(dāng)前線程,并且在收到其他線程的通知后只有當(dāng) pred 為 true 時才會被解除阻塞。因此第二種情況類似以下代碼:
while (!pred()) wait(lck);
std::function介紹
使用std::function可以將普通函數(shù),lambda表達(dá)式和函數(shù)對象類統(tǒng)一起來。它們并不是相同的類型,然而通過function模板類,可以轉(zhuǎn)化為相同類型的對象(function對象),從而放入一個vector或其他容器里,方便回調(diào)。
代碼實(shí)現(xiàn):
#pragma once
#ifndef MESSAGE_QUEUE_H
#define MESSAGE_QUEUE_H
#include <queue>
#include <mutex>
#include <condition_variable>
template<class Type>
class CMessageQueue
{
public:
CMessageQueue& operator = (const CMessageQueue&) = delete;
CMessageQueue(const CMessageQueue& mq) = delete;
CMessageQueue() :_queue(), _mutex(), _condition(){}
virtual ~CMessageQueue(){}
void Push(Type msg){
std::lock_guard <std::mutex> lock(_mutex);
_queue.push(msg);
//當(dāng)使用阻塞模式從消息隊(duì)列中獲取消息時,由condition在新消息到達(dá)時提醒等待線程
_condition.notify_one();
}
//blocked定義訪問方式是同步阻塞或者非阻塞模式
bool Pop(Type& msg, bool isBlocked = true){
if (isBlocked)
{
std::unique_lock <std::mutex> lock(_mutex);
while (_queue.empty())
{
_condition.wait(lock);
}
//注意這一段必須放在if語句中,因?yàn)閘ock的生命域僅僅在if大括號內(nèi)
msg = std::move(_queue.front());
_queue.pop();
return true;
}
else
{
std::lock_guard<std::mutex> lock(_mutex);
if (_queue.empty())
return false;
msg = std::move(_queue.front());
_queue.pop();
return true;
}
}
int32_t Size(){
std::lock_guard<std::mutex> lock(_mutex);
return _queue.size();
}
bool Empty(){
std::lock_guard<std::mutex> lock(_mutex);
return _queue.empty();
}
private:
std::queue<Type> _queue;//存儲消息的隊(duì)列
mutable std::mutex _mutex;//同步鎖
std::condition_variable _condition;//實(shí)現(xiàn)同步式獲取消息
};
#endif//MESSAGE_QUEUE_H
線程池可以直接在構(gòu)造函數(shù)中構(gòu)造線程,并傳入回調(diào)函數(shù),也可以寫一個Run函數(shù)顯示調(diào)用。這里我們選擇了第二種,對比:
- 在handler函數(shù)外部做循環(huán)接受消息,當(dāng)消息到達(dá)后調(diào)用hanlder處理。這種實(shí)現(xiàn)在上層做封裝,但是會在線程中頻繁的切換調(diào)用函數(shù)。這種設(shè)計(jì)無法復(fù)用一些資源,如當(dāng)在handler中做數(shù)據(jù)庫操作時,需要頻繁的連接和斷開連接,可以通過定義兩個虛函數(shù)Prehandler和AfterHandler來實(shí)現(xiàn)。
?。?!構(gòu)造函數(shù)中調(diào)用虛函數(shù)并不會能真正的調(diào)用子類的實(shí)現(xiàn)!?。?br /> 雖然可以對虛函數(shù)進(jìn)行實(shí)調(diào)用,但程序員編寫虛函數(shù)的本意應(yīng)該是實(shí)現(xiàn)動態(tài)聯(lián)編。在構(gòu)造函數(shù)中調(diào)用虛函數(shù),函數(shù)的入口地址是在編譯時靜態(tài)確定的,并未實(shí)現(xiàn)虛調(diào)用 - 寫一個Run函數(shù),將這一部分實(shí)現(xiàn)放在run函數(shù)中,顯示調(diào)用。
《Effective C++ 》條款9:永遠(yuǎn)不要在構(gòu)造函數(shù)或析構(gòu)函數(shù)中調(diào)用虛函數(shù)
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <functional>
#include <vector>
#include <thread>
#include "MessageQueue.h"
#define MIN_THREADS 1
template<class Type>
class CThreadPool
{
CThreadPool& operator = (const CThreadPool&) = delete;
CThreadPool(const CThreadPool& other) = delete;
public:
CThreadPool(int32_t threads,
std::function<void(Type& record, CThreadPool<Type>* pSub)> handler);
virtual ~CThreadPool();
void Run();
virtual void PreHandler(){}
virtual void AfterHandler(){}
void Submit(Type record);
private:
bool _shutdown;
int32_t _threads;
std::function<void(Type& record, CThreadPool<Type>* pSub)> _handler;
std::vector<std::thread> _workers;
CMessageQueue<Type> _tasks;
};
template<class Type>
CThreadPool<Type>::CThreadPool(int32_t threads,
std::function<void(Type& record, CThreadPool<Type>* pSub)> handler)
:_shutdown(false),
_threads(threads),
_handler(handler),
_workers(),
_tasks()
{
//第一種實(shí)現(xiàn)方案,注意這里的虛函數(shù)調(diào)用不正確
/*if (_threads < MIN_THREADS)
_threads = MIN_THREADS;
for (int32_t i = 0; i < _threads; i++)
{
_workers.emplace_back(
[this]{
PreHandler();
while (!_shutdown){
Type record;
_tasks.Pop(record, true);
_handler(record, this);
}
AfterHandler();
}
);
}*/
}
//第二種實(shí)現(xiàn)方案
template<class Type>
void CThreadPool<Type>::Run()
{
if (_threads < MIN_THREADS)
_threads = MIN_THREADS;
for (int32_t i = 0; i < _threads; i++)
{
_workers.emplace_back(
[this]{
PreHandler();
while (!_shutdown){
Type record;
_tasks.Pop(record, true);
_handler(record, this);
}
AfterHandler();
}
);
}
}
template<class Type>
CThreadPool<Type>::~CThreadPool()
{
for (std::thread& worker : _workers)
worker.join();
}
template<class Type>
void CThreadPool<Type>::Submit(Type record)
{
_tasks.Push(record);
}
#endif // !THREAD_POOL_H
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
C語言實(shí)現(xiàn)關(guān)機(jī)小程序
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)關(guān)機(jī)小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-02-02
C++ 實(shí)現(xiàn)旋轉(zhuǎn)蛇錯覺的詳細(xì)代碼
這篇文章主要介紹了C++ 實(shí)現(xiàn)旋轉(zhuǎn)蛇錯覺的詳細(xì)代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09
C++構(gòu)造析構(gòu)賦值運(yùn)算函數(shù)應(yīng)用詳解
構(gòu)造函數(shù)主要作用在于創(chuàng)建對象時為對象的成員屬性賦值,構(gòu)造函數(shù)由編譯器自動調(diào)用,無須手動調(diào)用;析構(gòu)函數(shù)主要作用在于對象銷毀前系統(tǒng)自動調(diào)用,執(zhí)行一 些清理工作2022-09-09
C++中四種對象生存期和作用域以及static的用法總結(jié)分析
以下是對C++中四種對象生存期和作用域以及static的用法進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下2013-09-09
Cocos2d-x學(xué)習(xí)入門之HelloWorld程序
這篇文章主要介紹了Cocos2d-x學(xué)習(xí)入門之HelloWorld程序,是學(xué)習(xí)Cocos2d-x的入門程序,其重要性不言而喻,需要的朋友可以參考下2014-08-08

