利用C++如何實(shí)現(xiàn)一個(gè)阻塞隊(duì)列詳解
阻塞隊(duì)列是多線(xiàn)程中常用的數(shù)據(jù)結(jié)構(gòu),對(duì)于實(shí)現(xiàn)多線(xiàn)程之間的數(shù)據(jù)交換、同步等有很大作用。
阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景,生產(chǎn)者是向隊(duì)列里添加元素的線(xiàn)程,消費(fèi)者是從隊(duì)列里取元素的線(xiàn)程。簡(jiǎn)而言之,阻塞隊(duì)列是生產(chǎn)者用來(lái)存放元素、消費(fèi)者獲取元素的容器。
考慮下,這樣一個(gè)多線(xiàn)程模型,程序有一個(gè)主線(xiàn)程 master 和一些 worker 線(xiàn)程,master 線(xiàn)程負(fù)責(zé)接收到數(shù)據(jù),給 worker 線(xiàn)程分配數(shù)據(jù),worker 線(xiàn)程取得一個(gè)任務(wù)后便可以開(kāi)始工作,如果沒(méi)有任務(wù)便阻塞住,節(jié)約 cpu 資源。
- master 線(xiàn)程 (生產(chǎn)者):負(fù)責(zé)往阻塞隊(duì)列中塞入數(shù)據(jù),并喚醒正在阻塞的 worker 線(xiàn)程。
- workder 線(xiàn)程(消費(fèi)者):負(fù)責(zé)從阻塞隊(duì)列中取數(shù)據(jù),如果沒(méi)有數(shù)據(jù)便阻塞,直到被 master 線(xiàn)程喚醒。
那么怎樣的數(shù)據(jù)結(jié)構(gòu)比較適合做這樣的喚醒呢?顯而易見(jiàn),是條件變量,在 c++ 11 中,stl 已經(jīng)引入了線(xiàn)程支持庫(kù)。
C++11 中條件變量
條件變量一般與一個(gè) 互斥量 同時(shí)使用,使用時(shí)需要先給互斥量上鎖,然后條件變量會(huì)檢測(cè)是否滿(mǎn)足條件,如果不滿(mǎn)足條件便會(huì)暫時(shí)釋放鎖,然后阻塞線(xiàn)程。
c++ 11使用方法主要如下:
#include <mutex>
#include <condition_value>
// 互斥量與條件變量
std::mutex m_mutex;
std::condition_value m_condition;
// 請(qǐng)求信號(hào)的一方
std::unique_lock<std::mutex> lock(mutex);
while(xxx)
{
// 這里會(huì)先釋放 lock,
// 如果有信號(hào)喚醒的話(huà),會(huì)重新加鎖。
m_condition.wait(lock);
}
// 發(fā)送消息進(jìn)行同步的一方
{
std::unique_lock<std::mutex> lock(mutex);
// 喚醒其他正在 wait 的線(xiàn)程
m_condition.notify_all();
}
用 C++11 實(shí)現(xiàn)阻塞隊(duì)列
我們使用條件變量包裝 STL 中的 queue 就可以實(shí)現(xiàn)阻塞隊(duì)列功能,如果有興趣,甚至可以自己實(shí)現(xiàn)一個(gè)效率更高的隊(duì)列數(shù)據(jù)結(jié)構(gòu)。
我們先假設(shè)一下阻塞隊(duì)列需要如下接口:
- push 將一個(gè)變量塞入隊(duì)列;
- take 從隊(duì)列中取出一個(gè)元素;
- size 查看隊(duì)列有多少個(gè)元素;
template <typename T>
class BlockingQueue
{
public:
BlockingQueue();
void push(T&& value);
T take();
size_t size() const;
private:
// 實(shí)際使用的數(shù)據(jù)結(jié)構(gòu)隊(duì)列
std::queue<T> m_data;
// 條件變量
std::mutex m_mutex;
std::condition_variable m_condition;
};
push 一個(gè)變量時(shí),我們需要先加鎖,加鎖成功后才可以壓入變量,這是為了線(xiàn)程安全。壓入變量后,就可以發(fā)送信號(hào)通知正在阻塞的條件變量。
void push(T&& value)
{
// 往隊(duì)列中塞數(shù)據(jù)前要先加鎖
std::unique_lock<std::mutex> lock(m_mutex);
m_data.push(value);
// 喚醒正在阻塞的條件變量
m_condition.notify_all();
}
take 一個(gè)變量時(shí),就要有些不一樣:
- 先加鎖,加鎖成功后,如果隊(duì)列不為空,可以直接取數(shù)據(jù),不需要 wait;
- 如果隊(duì)列為空呢,則 wait 等待,直到被喚醒;
- 考慮特殊情況,喚醒后隊(duì)列依然是空的……
T take()
{
std::unique_lock<std::mutex> lock(m_mutex);
while(m_data.empty())
{
// 等待
m_condition.wait(lock);
}
assert(!m_data.empty());
T value(std::move(m_data.front()));
m_data.pop();
return value;
}
總結(jié)下,代碼如下:
#ifndef BLOCKINGQUEUE_H
#define BLOCKINGQUEUE_H
#include <queue>
#include <mutex>
#include <condition_variable>
#include <assert.h>
template <typename T>
class BlockingQueue
{
public:
BlockingQueue()
:m_mutex(),
m_condition(),
m_data()
{
}
// 禁止拷貝構(gòu)造
BlockingQueue(BlockingQueue&) = delete;
~BlockingQueue()
{
}
void push(T&& value)
{
// 往隊(duì)列中塞數(shù)據(jù)前要先加鎖
std::unique_lock<std::mutex> lock(m_mutex);
m_data.push(value);
m_condition.notify_all();
}
void push(const T& value)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_data.push(value);
m_condition.notify_all();
}
T take()
{
std::unique_lock<std::mutex> lock(m_mutex);
while(m_data.empty())
{
m_condition.wait(lock);
}
assert(!m_data.empty());
T value(std::move(m_data.front()));
m_data.pop();
return value;
}
size_t size() const
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_data.size();
}
private:
// 實(shí)際使用的數(shù)據(jù)結(jié)構(gòu)隊(duì)列
std::queue<T> m_data;
// 條件變量的鎖
std::mutex m_mutex;
std::condition_variable m_condition;
};
#endif // BLOCKINGQUEUE_H
實(shí)驗(yàn)代碼
我們寫(xiě)個(gè)簡(jiǎn)單的程序?qū)嶒?yàn)一下,下面程序有 一個(gè) master 線(xiàn)程,5個(gè) worker 線(xiàn)程,master線(xiàn)程生成一個(gè)隨機(jī)數(shù),求 0-隨機(jī)數(shù) 的和。
#include <iostream>
#include <thread>
#include <mutex>
#include <random>
#include <windows.h>
#include <blockingqueue.h>
using namespace std;
int task=100;
BlockingQueue<int> blockingqueue;
std::mutex mutex_cout;
void worker()
{
int value;
thread::id this_id = this_thread::get_id();
while(true)
{
value = blockingqueue.take();
uint64_t sum = 0;
for(int i = 0; i < value; i++)
{
sum += i;
}
// 模擬耗時(shí)操作
Sleep(100);
std::lock_guard<mutex> lock(mutex_cout);
std::cout << "workder: " << this_id << " "
<< __FUNCTION__
<< " line: " << __LINE__
<< " sum: " << sum
<< std::endl;
}
}
void master()
{
srand(time(nullptr));
for(int i = 0; i < task; i++)
{
blockingqueue.push(rand()%10000);
printf("%s %d %i\n",__FUNCTION__, __LINE__, i);
Sleep(20);
}
}
int main()
{
thread th_master(master);
std::vector<thread> th_workers;
for(int i =0; i < 5; i++)
{
th_workers.emplace_back(thread(worker));
}
th_master.join();
return 0;
}
從輸出結(jié)果可以看出,master 線(xiàn)程將任務(wù)分配給了正在空閑的 worker 線(xiàn)程,具體是哪個(gè)線(xiàn)程就看操作系統(tǒng)的隨機(jī)調(diào)度了。
master 46 5
worker: 3 worker line: 34 sum: 20998440
master 46 6
worker: 7 worker line: 34 sum: 3308878
master 46 7
worker: 4 worker line: 34 sum: 34598721
master 46 8
worker: 6 worker line: 34 sum: 1563796
master 46 9
worker: 5 worker line: 34 sum: 27978940
Reference
總結(jié)
到此這篇關(guān)于利用C++如何實(shí)現(xiàn)一個(gè)阻塞隊(duì)列的文章就介紹到這了,更多相關(guān)C++實(shí)現(xiàn)阻塞隊(duì)列內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- c++優(yōu)先隊(duì)列用法知識(shí)點(diǎn)總結(jié)
- C++實(shí)現(xiàn)循環(huán)隊(duì)列
- c++優(yōu)先隊(duì)列(priority_queue)用法詳解
- C++基礎(chǔ)學(xué)習(xí)之利用兩個(gè)棧實(shí)現(xiàn)一個(gè)隊(duì)列
- C++用兩個(gè)棧實(shí)現(xiàn)一個(gè)隊(duì)列(面試官的小結(jié))
- C++利用兩個(gè)棧實(shí)現(xiàn)隊(duì)列的方法
- C++基于消息隊(duì)列的多線(xiàn)程實(shí)現(xiàn)示例代碼
- 在編程語(yǔ)言中怎樣定義隊(duì)列及其使用(C++)
- 淺談C++STL之雙端隊(duì)列容器
相關(guān)文章
C語(yǔ)言中函數(shù)棧幀的創(chuàng)建和銷(xiāo)毀的深層分析
在C語(yǔ)言中,每一個(gè)正在運(yùn)行的函數(shù)都有一個(gè)棧幀與其對(duì)應(yīng),棧幀中存儲(chǔ)的是該函數(shù)的返回地址和局部變量。從邏輯上講,棧幀就是一個(gè)函數(shù)執(zhí)行的環(huán)境:函數(shù)參數(shù)、函數(shù)的局部變量、函數(shù)執(zhí)行完后返回到哪里等等2022-04-04
C++ 面向?qū)ο蟪绦蛟O(shè)計(jì)--內(nèi)存分區(qū)詳解
這篇文章主要介紹了剖析C++的面向?qū)ο缶幊趟枷?C++的面向?qū)ο筇匦允瞧鋵?duì)C語(yǔ)言的重要拓展之處,需要的朋友可以參考下,希望能夠給你帶來(lái)幫助2021-08-08
基于C語(yǔ)言實(shí)現(xiàn)UDP客戶(hù)端
UDP是一種面向無(wú)連接的傳輸層協(xié)議,廣泛應(yīng)用于實(shí)時(shí)性要求較高的場(chǎng)景,本文將介紹如何使用C語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的UDP客戶(hù)端程序,有需要的可以參考下2024-10-10
C語(yǔ)言?huà)呃子螒虻膶?shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言?huà)呃子螒虻膶?shí)現(xiàn)方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
QT網(wǎng)絡(luò)編程Tcp下C/S架構(gòu)的即時(shí)通信實(shí)例
下面小編就為大家?guī)?lái)一篇QT網(wǎng)絡(luò)編程Tcp下C/S架構(gòu)的即時(shí)通信實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
C++可變參數(shù)函數(shù)的實(shí)現(xiàn)方法示例
這篇文章主要給大家介紹了關(guān)于C++可變參數(shù)函數(shù)的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
C語(yǔ)言實(shí)現(xiàn)Linux下的socket文件傳輸實(shí)例
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)Linux下的socket文件傳輸?shù)姆椒?較為詳細(xì)的分析了C語(yǔ)言文件Socket文件傳輸客戶(hù)端與服務(wù)器端相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-06-06

