生產者消費者概念
在實際的開發中,通常會碰到一個場景,有一個模塊產生數據被稱爲生產者,另一個模塊來接受數據被稱爲消費者(此處的模塊是指廣義的模塊 函數,類,進程等),但是有生產者和消費者的場景還不能稱爲生產者消費者模型,還需要一個緩衝區在生產者和消費者之間作爲中介,生產者把數據放到緩衝區中,消費者從緩衝區中取數據。
這個關係類似生活中我們到超市買東西,供貨商將商品送到超市,我們就是消費者,供貨商就是生產者,超市就是中間的緩衝區。
生產者消費者模型優點
具體優點可分爲以下幾點
1.解耦
假設生產者和消費者是兩個類,消費者直接調用生產者,這兩個類就是產生了耦合,假如生產者改變,消費者也會產生影響,當生產者和消費者中間有一層緩衝區,兩者不會直接依賴,耦合度就降低了。
2.支持併發操作
消費者調用生產者,還有另一個問題,因爲函數調用時同步的,在生產者沒有返回數據單位前,消費者只能在旁邊等待,浪費時間。
在使用生產者/消費者模式時,生產者和消費者是兩個獨立的併發主體,生產者不停的將數據送進緩衝區中,基本不用考慮消費者的問題。
這個模式主要就是解決消費實例的問題的。
3.支持忙閒不均
緩衝區還有另一個好處,當生產者產生數據時快時慢,消費者來不及處理,可以將數據放在緩衝區中,等生產者的速度慢下來,消費者再慢慢處理。
數據單元
簡單的說,每次生產者放入緩衝區的數據,就是一個數據單元,消費者收到的數據也是一個數據單元
數據單元的特性
1.關係到業務對象
生產者消費者模型必須要關係到某種業務對象,在考慮問題時必須對生產者消費者模型的業務邏輯考慮清楚才能確定數據單元。
如果選擇錯誤業務對象,就會導致程序設計和代碼實習的複雜程度增加,提高開發和維護的成本。
2.完整性
所謂的完整性是指,在數據傳遞的過程中,要保持數據單元的完整性,生產者要麼將一個數據單元發給消費者,或者不發,沒有發給半個數據單元給消費者的情況。
類比寄信,往郵局進行必須給一封完整的信,否則就不給,沒有給半封的道理。
3.獨立性
所謂獨立性指的是各個數據單元相互獨立,發送失敗的數據單元不應該影響已發送的數據單元更不應該影響未發送的數據單元。
假設一種場景,假如生產者的生產數據單元的速度比消費者的處理速度,那麼數據會不斷堆積到緩衝區中,直到把緩衝區佔滿,新的數據單元無法傳進去,假如生產者和消費者相互獨立,耦合度較低,等生產者的速度降下來,新傳入的數據單元不應該被影響,反之,如果兩者間有耦合,失敗的數據單元會影響新傳入的數據單元,代碼會變複雜。
4.顆粒度
前面說到,數據單元和業務對象有關,在某些時候他們是一一對應的,但是有的時候,可能會把N個業務對象打包,變成一個數據單元。
舉個例子,例如美團外賣,一份訂單相當於一個業務對象,假如外賣員每接到一個訂單就立刻去送(顆粒度爲1),會損失潛在的其他訂單的收益,假如接到100個訂單後才送餐(顆粒度爲100),時間上又會浪費。
所以當顆粒度過小會造成性能上的損失,顆粒度過大又會造成某種資源的浪費(內存,套接字)。
123原則
在生產者消費者模型,可以用123原則記住三種關係,兩種對象,一箇中介。
- 三種關係,生產者之間的互斥,消費者之間的互斥,消費者和生產者之間的互斥和同步。
- 兩種對象,生產者和消費者
- 一箇中介,緩衝區。
如果有讀者對於互斥和同步不太清楚,可以看這篇博客淺談互斥和同步,可能需要一個梯子,這裏就不多說了。
基於阻塞隊列的單生產者單消費者
爲了實現簡單,下面的代碼用隊列來實現,爲了防止運行過快,用sleep函數,使得1秒運行一次,使用線程運行兩個代碼,用pthread_cond和pthread_mutex來進行通信,這裏的queue是共享資源。
#include <iostream>
#include <queue>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
#define NUM 8
class BlockQueue
{
private:
queue<int> q;
int cap;//容量
pthread_mutex_t lock;
pthread_cond_t full;
pthread_cond_t empty;
public:
BlockQueue(int _cap =NUM):cap(_cap)
{
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&full,NULL);
pthread_cond_init(&empty,NULL);
}
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
void pushData(const int& data)
{
pthread_mutex_lock(&lock);
while(q.size()== cap)
{
pthread_cond_signal(&full);
cout<<"queue is full ,stop porduct"<<endl;
pthread_cond_wait(&empty,&lock);
}
q.push(data);
pthread_mutex_unlock(&lock);
}
void popData(int & data)
{
pthread_mutex_lock(&lock);
while(q.size()== 0)
{
pthread_cond_signal(&empty);
cout<<"queue is empty ,stop consumer"<<endl;
pthread_cond_wait(&full,&lock);
}
q.pop();
data = q.front();
pthread_mutex_unlock(&lock);
}
};
void* producter(void* arg)
{
int data;
srand((unsigned long)time(NULL));
for(;;)
{
BlockQueue *bq = ( BlockQueue*) arg;
data = rand()%1024;
bq->pushData(data);
cout<<"the data is push done :"<<data<<endl;
sleep(1);
}
}
void* consumer(void *arg)
{
BlockQueue *bq = ( BlockQueue*) arg;
int data;
for(;;)
{ bq->popData(data);
cout<<"the data is pop done :"<<data<<endl;
sleep(1);
}
}
int main()
{
BlockQueue bd;
pthread_t c,p;
pthread_create(&c,NULL,consumer,(void*)&bd);//線程爲可分離的
pthread_create(&p,NULL,producter,(void*)&bd);
pthread_join(c,NULL);
pthread_join(p,NULL);
}
總結
生產者消費者模型包括生產者,消費者,緩衝區。
生產者將數據單位存到緩衝區中,消費者從緩衝區中拿數據單位。兩者並行獨立,耦合度低,假如有一方的實現改變,不會影響另一方,支持忙閒不均,當生產者的生產速度超過消費者的處理速度,可以把數據單位放在緩衝區中,暫時保存。
數據單位業務邏輯有關具有完整性,顆粒度,獨立性的特性。
上面的代碼是根據阻塞隊列原理產生的生產者消費者模型,在下篇博客中,我們將基於信號實現生產者消費者模型。