阻塞佇列是多執行緒中常用的資料結構,對於實現多執行緒之間的資料交換、同步等有很大作用。
阻塞佇列常用於生產者和消費者的場景,生產者是向佇列裡新增元素的執行緒,消費者是從佇列裡取元素的執行緒。簡而言之,阻塞佇列是生產者用來存放元素、消費者獲取元素的容器。
考慮下,這樣乙個多執行緒模型,程式有乙個主線程 master 和一些 worker 執行緒,master 執行緒負責接收到資料,給 worker 執行緒分配資料,worker 執行緒取得乙個任務後便可以開始工作,如果沒有任務便阻塞住,節約 cpu 資源。
那麼怎樣的資料結構比較適合做這樣的喚醒呢?顯而易見,是條件變數,在 c++ 11 中,stl 已經引入了執行緒支援庫。
條件變數一般與乙個互斥量同時使用,使用時需要先給互斥量上鎖,然後條件變數會檢測是否滿足條件,如果不滿足條件便會暫時釋放鎖,然後阻塞執行緒。
c++ 11使用方法主要如下:
#include
#include
// 互斥量與條件變數
std::mutex m_mutex;
std::condition_value m_condition;
// 請求訊號的一方
std::unique_lock
lock
(mutex)
;while
(***)
// 傳送訊息進行同步的一方
我們使用條件變數包裝 stl 中的 queue 就可以實現阻塞佇列功能,如果有興趣,甚至可以自己實現乙個效率更高的佇列資料結構。
我們先假設一下阻塞佇列需要如下介面:
push 將乙個變數塞入佇列;
take 從佇列中取出乙個元素;
size 檢視佇列有多少個元素;
template
<
typename t>
class
blockingqueue
;
push 乙個變數時,我們需要先加鎖,加鎖成功後才可以壓入變數,這是為了執行緒安全。壓入變數後,就可以傳送訊號通知正在阻塞的條件變數。
void
push
(t&& value)
take 乙個變數時,就要有些不一樣:
先加鎖,加鎖成功後,如果佇列不為空,可以直接取資料,不需要 wait;
如果隊列為空呢,則 wait 等待,直到被喚醒;
考慮特殊情況,喚醒後佇列依然是空的……
t take()
assert
(!m_data.
empty()
);t value
(std::
move
(m_data.
front()
)); m_data.
pop();
return value;
}
總結下,**如下:
#ifndef blockingqueue_h
#define blockingqueue_h
#include
#include
#include
#include
template
<
typename t>
class
blockingqueue
// 禁止拷貝構造
blockingqueue
(blockingqueue&)=
delete;~
blockingqueue()
void
push
(t&& value)
void
push
(const t& value)
t take()
assert
(!m_data.
empty()
);t value
(std::
move
(m_data.
front()
)); m_data.
pop();
return value;
} size_t size()
const
private
:// 實際使用的資料結構佇列
std::queue m_data;
// 條件變數的鎖
std::mutex m_mutex;
std::condition_variable m_condition;};
#endif
// blockingqueue_h
我們寫個簡單的程式實驗一下,下面程式有 乙個 master 執行緒,5個 worker 執行緒,master執行緒生成乙個隨機數,求 0-隨機數 的和。
#include
#include
#include
#include
#include
#include
using
namespace std;
int task=
100;
blockingqueue<
int> blockingqueue;
std::mutex mutex_cout;
void
worker()
// 模擬耗時操作
sleep
(100);
std::lock_guard
lock
(mutex_cout)
; std::cout <<
"workder: "
<< this_id <<
" "<< __function__
<<
" line: "
<<
__line__
<<
" sum: "
<< sum
<< std::endl;}}
void
master()
}int
main()
th_master.
join()
;return0;
}
從輸出結果可以看出,master 執行緒將任務分配給了正在空閒的 worker 執行緒,具體是哪個執行緒就看作業系統的隨機排程了。
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
條件變數 C 筆記 實現乙個環形阻塞佇列
環形阻塞佇列,顧名思義,首先,它是乙個佇列,然後,它應當是乙個環形,並且它是會進行阻塞的。但是根據我們的常識,記憶體位址是用乙個long long int來儲存的,我們儲存的資料的位址無法繞成乙個環,所以我們想要成環的話,需要我們自己去處理。如上圖,相比環狀實現的來說,資料在記憶體中的儲存更接近線性...
動手寫乙個阻塞佇列
之前看佇列,都是停留在看和使用的階段。再次看佇列的時候,忽然發現並沒有深入到底層。比如 阻塞佇列時如何阻塞的呢?是監聽,還是等待呢?然後看著看著就看到了lock和reentrantlock,為什麼不使用synchronized呢?為什麼使用condition,condition是什麼呢?wait,n...
C 實現乙個簡易的執行緒池
工作中需要用到多執行緒,就簡單實現了乙個簡易的執行緒池,直接上 記錄一下 ifndef threadpool h define threadpool h include include include include include include class threadpool endif th...