Linux 基於阻塞佇列的生產消費者模型

2021-09-25 05:01:40 字數 3842 閱讀 1696

談生產者消費者模型之前我們必須知道什麼是生產者消費者模型,看題目你可能覺得他是乙個十分高深的東西,但是或許我們身邊就存在生產者消費者模型。

打個比方,生活中我們經常在缺少生活用品或者其他商品的時候我們通常會選擇去超市,那也就不難理解,我們其實就是所謂的消費者。那生產者是超市麼?答案是否定的,如果超市是生產者,那供貨商是什麼,所以供貨商是所謂的生產者。再談超市,其實超市是為了生產者和消費者交易而誕生的,所以超市就是交易場所

將場景引入到我們的軟體開發中來,不難模擬,乙個生產資料的模組就是生產者,處理這些資料的模組可以認為是消費者,生產者生產的資料不直接交付給消費者,而是放入某個緩衝區,讓消費者自己來取,那麼這個緩衝區其實就可以認為是交易場所

綜上,乙個生產者消費者模型右三部分構成,生產資,消費者,交易場所。他們之間的關係就如同下圖。

相信身為程式設計師的你每天都會做一件事,而且日復一日,那就是使用乙個函式來呼叫另外乙個函式。如下圖,函式a將資料交給函式b處理,希望能的到結果返回,這裡非常型別生產者和消費者,但是唯一缺少的就是我們上面所說的交易場所。正是缺少了這個交易場所,導致函式呼叫時出現一系列的問題,你經常發現只要函式b一旦修改就導致函式a中的呼叫也要修改,並且每當出現函式b奔潰時函式a也瞬間掛掉。這其實並不是最大的問題,如圖,每當你呼叫函式b時,函式a就阻塞等待函式b,整個流程就變成了絕對的序列,函式b未執行完,函式a也將永遠卡死在呼叫處。

那麼解決方案其實就是需要把他們轉化為乙個生產消費者模型,為倆個函式之間也加上乙個交易場所,這個交易場所可以存放函式a需要讓函式b處理的資料,當交易場所存在資料時函式b就進行處理,否則函式a向交易場所新增資料,這個交易場所其實就是一段緩衝區,可以是鍊錶,陣列,佇列任何結構,這樣函式a和函式b的執行互不影響,並且大大增加了效率。

回到軟體開發的本身歸根結底來說,生產者消費者模式就是通過乙個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而通過阻塞佇列來進行通訊,所以生產者生產完資料之後不用等待消費者處理,直接扔給阻塞佇列,消費者不找生產者要資料,而是直接從阻塞佇列裡取,阻塞佇列就相當於乙個緩衝區,平衡了生產者和消費者的處理能力。這個阻塞佇列就是用來給生產者和消費者解耦的。

我們之前的部落格講了linux下的同步與互斥,所以這裡生產消費者模型也滿足其中的關係。很明顯生產者與生產者之間是互斥關係,因為他們之間存在一種競爭,他們都想自己生產物品供被人消費。而消費者與消費者也是互斥關係,很多同學可能會覺得這是不是出錯了,消費者之間怎麼能是互斥關係呢,那舉個例子,超市現在只剩最後一包速食麵了,然而你們都餓了,此時你們簡直是不是需要競爭呢?最後生產者和消費者比較獨特,他們既要互斥,還要同步,因為某乙個時刻他們如果同時想生產或者消費就會產生互斥現象,而且從上述例子中我們可以得出,交易場所其實是共享資源,共享資源必須合理的讓所有人都能訪問,這也就是為什麼需要同步。

這裡為了便於記憶,你可以將上面這段話總結為123原則每個生產消費者模型有1乙個交易場所,2個角色,3種關係。相信現在你對生產消費者模型有了新的理解,那麼我們接下來就來實現乙個生產消費者模型。

基於阻塞佇列的生產者消費者模型顯而易見,他最大的特點就在於他的交易場所使用了阻塞佇列,對於阻塞佇列你可能覺得陌生,不過你還記得linux下程序間通訊的匿名管道麼?哎呦,是不是有感覺了,程序1就是生產者,程序2就是消費者,匿名管道實質上是乙個檔案,也是乙個緩衝區,同樣是我們的交易場所。我們這裡實現的模型真的像極了程序間通訊的管道。

這裡我們實現了乙個非常簡單的阻塞佇列,這裡說明幾點問題,因為選擇了阻塞佇列作為了交易場所,所以我們就需要佇列的容器,但是原生的實現乙個容器並不是一件容易的事,所以我們沒有使用c語言,而是選擇了c++,這樣有的小夥伴看**可能有的地方看不懂,不過筆者盡量寫的非常偏c語言了,應該沒什麼大問題。

這裡實現阻塞佇列時為了簡單起見,我們只考慮交易空的情況,當交易場所為空時通知生產者生產,所以我們就只需要乙個條件變數即可,如果你想實現為空通知生產者生產,為滿通知消費者消費那麼你就需要再另外的增加乙個條件變數來控制。

以下的**因為沒有規定佇列的上限,所以為了避免生產者太快的情況我們讓生產者生產一次就睡眠一秒,所以最後模型執行起來以後的現象是生產者生產乙個資料,消費者就拿走乙個資料。追加說明一點,因為這裡的佇列其實是共享資源,所以我們需要使用一把鎖將他保護起來。

#include

#include

#include

#include

#include

#include

using namespace std;

class blockqueue

void

unlockqueue()

bool isempty()

void

threadwait()

void

wakeonethread()

public:

blockqueue()

void

pushdata

(const

int& data)

void

popdata

(int

& data)

data = q.

front()

; q.

pop();

unlockqueue()

; cout <<

"consume run done, data pop sucess : "

<< data <

blockqueue()

};void

*product

(void

* arg)

//生產者生產

}void

*consume

(void

* arg)

//消費者消費

}int

main()

**執行結果:

仔細閱讀上面的**你一定能看的懂,如果有興趣的同學還可以進一步修改**,因為這裡我們也並沒有維護生產者生產者,消費者消費者之間的關係,但是想維護這倆個關係也很簡單,你只需多建立幾個執行緒,然後再建立倆把鎖,再這些生產者生產者和消費者消費者競爭佇列資源時,加上鎖就可以。筆者這裡僅為大家說明概念,篇幅太長就不在實現。

ps:不知道有沒有小夥伴覺得從linux下修改拷貝**很麻煩,筆者偷偷告訴你個工具叫做gedit,這也是乙個編輯器,你如果想拷貝很長的**出來你用gedit + **檔案開啟檔案,開啟後直接用滑鼠拷貝貼上就好。昨天才無意中知道,真***的好用哈哈哈。

到這裡我們基於阻塞佇列的生產消費者模型就介紹完了,但是小夥伴們不需要太關心**的實現,你應該深刻理解這種模型的好處用途和思想,你要你記清楚筆者總結的那個123原則,相信能加深你對生產者消費者模型的記憶和理解,下篇部落格筆者帶領大家實現另一種更有意思的生產消費者模型。

Linux 基於阻塞佇列的生產消費者模型

在我們介紹今天的內容之前我們先了解一些相關的概念 linux執行緒基本概念 linux執行緒控制 linux執行緒互斥 linux執行緒同步 接下來我們進入今天的主題生產者消費者模型 生產者消費者模型概念 為什麼要使用生產者消費者模型 生產消費者模型的優點 基於阻塞佇列的生產者消費者模型 生產者消費...

Linux 基於環形佇列的生產消費模型

在上篇部落格基於阻塞佇列的生產者消費者模型中我介紹了什麼是生產者消費者模型,還沒了解的可以戳鏈結檢視。本篇部落格也是實現乙個生產者消費者模型,只是這篇部落格我將基於迴圈佇列實現生產消費模型。posix 訊號量 這裡要注意了,我們這次使用的是基於posix的訊號量,千萬不要誤認為是systemv的訊號...

基於陣列實現阻塞佇列

基於陣列實現的話,需要額外兩個指標,乙個指向頭元素,乙個指向尾元素。出的時候從頭元素出去,入的時候從尾元素入。即出的時候tail指標 1,入的時候尾指標 1 因此使用乙個環形佇列最好,不會浪費空間也不需要挪動元素位置。因為是環形佇列,因此tail在最後乙個位置的時候,再進來乙個元素,如果佇列不滿,就...