【Linux】生產者消費者模型介紹

基本概念

說起生產者消費者模型,我們將該模型理解爲商店的供貨者和前來購買商品的消費者。

他們需要通過商店提供的緩衝區(貨架)來進行貨物的擺放和購買。

假設沒有這個貨架,那麼生產者只能生產一個商品,直到等到消費者購買後,纔可以進行生產,這樣的效率是很低的。

我們可以通過一個圖片來理解此模型


三種關係

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;
}

運行結果


如果將代碼中註釋的時間做修改,使得生產者的生產速度高於消費者,那麼運行結果爲下圖


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章