生产者消费者模型
互斥同步篇
三种关系两个角色一个场所。
生产者和消费者对临界区的访问是互斥的,各自也是互斥的。
例如链表生产节点的例子,生产者往链表里push,消费者pop,这时他们共同操纵的链表就是临界资源,不能让生产者消费者同时进入临界区,需要加以互斥条件。又假如生产者慢,消费者快,必须对消费者使用同步限制,不然产生饥饿现象。
第一个版本,不加互斥限制
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct Node{
int data;
struct Node* pNext;
}node,*pNode;
pNode pHead = NULL;
void init_llist(pNode *pHead)
{
*pHead = (pNode)malloc(sizeof(node));
if(NULL == *pHead){
exit(1);
}
(*pHead)->pNext = NULL;
}
void show_llist(pNode pHead)
{
if(pHead == NULL){
return ;
}
pNode pCur = pHead->pNext;
while(pCur){
printf("%d->", pCur->data);
pCur = pCur->pNext;
}
printf("\n");
}
void push_llist(pNode pHead, int data)
{
pNode pNew = NULL;
pNew = (pNode)malloc(sizeof(node));
pNew->data = data;
if(pNew != NULL){
pNew->pNext = pHead->pNext;
pHead->pNext = pNew;
printf("生产者放入了一个%d\n", pNew->data);
}
}
void pop_llist(pNode pHead)
{
if(pHead->pNext == NULL){
printf("消费者来消费了,但是没有东西可拿\n");
return ;
}
pNode pDel = pHead->pNext;
printf("消费者拿走%d\n", pDel->data);
pHead->pNext = pHead->pNext->pNext;
free(pDel);
}
void* productor()
{
srand((unsigned)time(NULL));
//每过0.5秒生产一个
while(1)
{
int data = rand()%100 +1;
push_llist(pHead, data);
show_llist(pHead);
fflush(stdout);
usleep(500000);
}
return NULL;
}
void* consumer()
{
while(1){
//每过0.5秒消费一个
pop_llist(pHead);
usleep(500000);
}
return NULL;
}
int main()
{
//带头结点的单链表
init_llist(&pHead);
//创建2个线程
pthread_t tidA, tidB;
void *ret;
pthread_create(&tidA, NULL, productor, NULL);
pthread_create(&tidB, NULL, consumer, NULL);
pthread_join(tidA, &ret);
free(ret);
pthread_join(tidB, &ret);
free(ret);
}
打印结果:
消费者来消费了,但是没有东西可拿
生产者放入了一个18
18->
消费者拿走18
生产者放入了一个17
0->
第一个18正常,然后生产者放了17,链表却被修改成了0,发生错乱,这就是两个进程同时访问临界区的后果。
加入互斥条件
对临界区进行限制就不会出错了
pthread_mutex_t mutex;
void* productor()
{
srand((unsigned)time(NULL));
//每过3秒生产一个
while(1)
{
pthread_mutex_lock(&mutex);
int data = rand()%100 +1;
push_llist(pHead, data);
show_llist(pHead);
fflush(stdout);
pthread_mutex_unlock(&mutex);
usleep(2000000);
}
return NULL;
}
void* consumer()
{
while(1){
pthread_mutex_lock(&mutex);
pop_llist(pHead);
pthread_mutex_unlock(&mutex);
usleep(500000);
}
return NULL;
}
生产者放入了一个45
45->
消费者拿走45
消费者来消费了,但是没有东西可拿
消费者来消费了,但是没有东西可拿
消费者来消费了,但是没有东西可拿
加入了互斥条件,这样怎么都不会再出错了。
但现在有新的问题,消费者频繁的抢占对临界区的访问却拿不到资源,这就极大的浪费了系统资源。所以需要对临界资源实现有序访问,加入线程同步。
加入线程同步
pthread_mutex_t mutex;
pthread_cond_t cond;
void* productor()
{
srand((unsigned)time(NULL));
//每过3秒生产一个
while(1)
{
pthread_mutex_lock(&mutex);
int data = rand()%100 +1;
push_llist(pHead, data);
show_llist(pHead);
fflush(stdout);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
usleep(2000000);
}
return NULL;
}
void* consumer()
{
while(1){
pthread_mutex_lock(&mutex);
while(pHead->pNext == NULL)
pthread_cond_wait(&cond, &mutex);
pop_llist(pHead);
pthread_mutex_unlock(&mutex);
usleep(500000);
}
return NULL;
}
生产者放入了一个87
87->
消费者拿走87
生产者放入了一个26
26->
消费者在进到临界区后,对临界资源进行判断,如果条件不满足,就挂起等待需求的资源。
信号量版
信号量是用来做线程同步的,因为多线程看待信号量也是临界资源,因此想要起到同步进程的作用首先得保证信号量自己是原子性的。
信号量的本质就是计数器,通过描述临界资源的数量来同步线程,只要线程申请到信号量,在临界区内一定有一个资源属于线程。
一个放苹果的例子
一个环形队列,假设有6个空盘子,一个人不断往盘子里放苹果,一个人跟在屁股后面拿苹果,还要保证2个规则
- 放苹果的人只能往空盘子里放
- 拿苹果的人只能从有苹果的盘子拿
再设想两种情况
- 一开始没有苹果的时候,只允许放苹果的人操作
- 放满的时候只允许拿苹果的人操作
这里苹果和6个盘子就是临界资源,放盘子的桌子是临界区,两个人即使同时进入临界区也没关系,因为操纵各自的临界资源并不会冲突,即使两个人操作到同一个盘子也规定好了谁拿谁放,所以并不需要线程互斥,只需要同步就好了。