C 實現乙個簡易的阻塞佇列

2021-10-09 23:07:54 字數 3562 閱讀 9347

阻塞佇列是多執行緒中常用的資料結構,對於實現多執行緒之間的資料交換、同步等有很大作用。

阻塞佇列常用於生產者和消費者的場景,生產者是向佇列裡新增元素的執行緒,消費者是從佇列裡取元素的執行緒。簡而言之,阻塞佇列是生產者用來存放元素、消費者獲取元素的容器。

考慮下,這樣乙個多執行緒模型,程式有乙個主線程 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...