基本概念
說起生產者消費者模型,我們將該模型理解爲商店的供貨者和前來購買商品的消費者。
他們需要通過商店提供的緩衝區(貨架)來進行貨物的擺放和購買。
假設沒有這個貨架,那麼生產者只能生產一個商品,直到等到消費者購買後,纔可以進行生產,這樣的效率是很低的。
我們可以通過一個圖片來理解此模型
三種關係
1、生產者和生產者
互斥關係,一個貨架上不能同時讓兩個或者多個生產者來放入數據
2、消費者和消費者
互斥關係,兩個或多個消費者不能同時獲取貨架上的一個商品
3、生產者和消費者
互斥和同步的關係。消費者在消費時,生產者不能放入。同樣,生產者在放商品時,消費者不能拿走,這說明了是互斥;而除此之外,生產者必須先生產商品,纔可以讓消費者消費,這樣的按照順序訪問資源的關係稱之爲同步。
利用單鏈表進行模型模擬
原理
用兩個線程來分別表示生產者和消費者,用單鏈表來表示中間的緩衝區
當生產者生產數據時,將新的數據放入單鏈表的頭部。
當消費者消費數據時,將頭部的數據彈出。
所用函數
pthread_mutex_t
初始化函數
加鎖函數
lock爲加鎖,unlock爲解鎖
如果一個線程想要獲取鎖,又不想掛起等待,則調用trylock
條件變量
pthread_cond_t
條件變量的初始化
線程等待
操作方法
1、釋放Mutex
2、阻塞等待
3、當被喚醒時,重新獲得Mutex並返回。
喚醒等待的消費者線程
代碼實現
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t mycond = PTHREAD_COND_INITIALIZER;
//定義節點的結構體
typedef struct node
{
int _data;
struct node* _next;
}node,*pNode;
//定義鏈表的結構體
typedef struct LinkList
{
node* phead;
}LinkList,*pLinkList;
//初始化頭節點
void InitList(pLinkList plist)
{
assert(plist);
plist->phead = NULL;
}
//初始化並返回新的節點
pNode buyNewNode(int data)
{
pNode newnode = (pNode)malloc(sizeof(node));
if(newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->_data = data;
newnode->_next = NULL;
return newnode;
}
//頭節點前進行插入
void pushFront(pLinkList pList,int data)
{
//產生新的節點
pNode newNode = buyNewNode(data);
//判斷有無元素
if(pList->phead == NULL)
{
pList->phead = newNode;
return;
}
//進行頭插
pNode pFristNode = pList->phead;
newNode->_next = pFristNode;
pList->phead = newNode;
}
//彈出一個頭節點,data用來保存彈出的元素
void popFront(pLinkList pList,int* data)
{
assert(pList);
pNode delNode = pList->phead;
if(delNode == NULL)
return;
*data = delNode->_data;
pList->phead = delNode->_next;
delNode->_next = NULL;
free(delNode);
delNode = NULL;
}
//銷燬單鏈表
void DestroyList(pLinkList pList)
{
assert(pList);
pNode cur = pList->phead;
pNode delNode = NULL;
while(cur)
{
delNode = cur;
cur = cur->_next;
delNode->_next = NULL;
free(delNode);
}
pList->phead = NULL;
}
//打印單鏈表中的內容
void ShowList(pLinkList pList)
{
assert(pList);
pNode cur = pList->phead;
while(cur)
{
printf("%d ",cur->_data);
cur = cur->_next;
}
printf("\n");
}
void* producterThread(void* arg)
{
pLinkList list = (pLinkList)arg;
int data = 0;
while(1)
{
sleep(1);
pthread_mutex_lock(&mylock);//訪問臨界區前進行加鎖
data = rand()%100;
pushFront(list,data);//生產者進行生產
pthread_cond_signal(&mycond);//生產完畢後喚醒在該條件
pthread_mutex_unlock(&mylock);//訪問完畢後解鎖
printf("生產者生產了: %d \n",data);
}
}
void* consumerThread(void* arg)
{
pLinkList list = (pLinkList)arg;
int data = 0;
while(1)
{
sleep(1);
pthread_mutex_lock(&mylock);//訪問臨界區前進行加鎖
while(list->phead == NULL)//如果緩存區中沒有數據,則進行等待
{
pthread_cond_wait(&mycond,&mylock);
}
popFront(list,&data);//消費者進行消費
pthread_mutex_unlock(&mylock);//訪問完畢,進行解鎖
printf("消費者進行消費 : %d\n",data);
}
}
int main()
{
LinkList list;
InitList(&list);
//創建消費者和生產者兩個線程
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,producterThread,(void*)&list);
pthread_create(&tid2,NULL,consumerThread,(void*)&list);
//等待線程的結束回收進程
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
DestroyList(&list);
return 0;
}
運行結果
基於環形隊列的多生產者多消費者模型
原理
1、生產者先進行生產
2、消費者消費的數據不能夠超過生產者
3、生產者生產數據不能比消費者快一圈
圖解
代碼實現
#include<stdio.h>
#include<semaphore.h>//信號量包含的頭文件
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#define _SIZE_ 64
int ringBuf[_SIZE_];//定義環形隊列,64爲環形隊列的長度
//定義兩個信號量
sem_t semBlack;//格子
sem_t semData;//數據
sem_t proLock;//生產者之間的互斥
sem_t conLock;//消費者之間的互斥
//消費者
void* consumer(void* arg)
{
//成功返回0,失敗返回錯誤碼
pthread_detach(pthread_self());
static int i = 0;
int id = (int)arg;
while(1)
{
//sleep(1);
usleep(1000);
sem_wait(&semData);//對於臨界區數據進行P操作
sem_wait(&conLock);//對於消費者互斥鎖進行P操作
printf("消費者 %d 消費了: %d , tid : %lu\n",id,ringBuf[i++],pthread_self());
i %= _SIZE_;
sem_post(&conLock);//對於消費者互斥鎖進行V操作
sem_post(&semBlack);//對於臨界區數據進行V操作
}
}
void* producter(void* arg)
{
pthread_detach(pthread_self());
static int i = 0;
int id = (int)arg;
while(1)
{
sleep(1);
//usleep(1000);
sem_wait(&semBlack);//對臨界區數據進行P操作
sem_wait(&proLock);//對生產者互斥鎖進行P操作
int num = rand()%100;
ringBuf[i++] = num;
printf("生產者 %d 生產了 : %d , tid : %lu\n",id, num ,pthread_self());
i %= _SIZE_;
sem_post(&proLock);//對生產者互斥鎖進行V操作
sem_post(&semData);//對臨界區數據進行V操作
}
}
int main()
{
//分別定義兩個生產者和兩個消費者
pthread_t con0,con1,pro0,pro1;
//初始化信號量
sem_init(&semData,0,0);
sem_init(&semBlack,0,_SIZE_);
sem_init(&proLock,0,1);
sem_init(&conLock,0,1);
int i = 0;
pthread_create(&pro0,NULL,producter,(void*)i);
pthread_create(&con0,NULL,consumer,(void*)i);
i = 1;
pthread_create(&pro1,NULL,producter,(void*)i);
pthread_create(&con1,NULL,consumer,(void*)i);
//銷燬信號量
sem_destroy(&semBlack);
sem_destroy(&semData);
pthread_join(pro0,NULL);
pthread_join(con0,NULL);
return 0;
}
運行結果
如果將代碼中註釋的時間做修改,使得生產者的生產速度高於消費者,那麼運行結果爲下圖