生产者与消费者模式(线程的同步与互斥)

条件变量

条件变量的提出首先要涉及一个概念,就是生产者消费者模型:


生产者消费者,是在多线程同步的一个问题,两个固定大小缓冲区的线程,在实际运行是会发生问题,生产者是生成数据放入缓冲区,重复过程,消费者在缓冲区取走数据。

生产者消费者的模型提出了三种关系,两种角色,一个场所

三种关系: 
- 生产者之间的互斥关系 
- 消费者之间的竞互斥关系 
- 生产者和消费者之间互斥和同步关系(同一时刻只能有一个,要么在生产,要么在消费,这就是互斥关系,只能在生产者生产完了之后才能消费,这就是同步关系)

两个角色:一般是用进程或线程来承担生产者或消费者

一个场所:有效的内存区域。(如单链表,数组)
我们就可以把这个想象成生活中的超市供货商,超市,顾客的关系,超市供货商供货,超市是摆放货物的场所,然后用户就是消费的。
条件变量属于线程的一种同步的机制,条件变量与互斥锁一起使用,可以使得线程进行等待特定条件的发生。条件本身是由互斥量保护的,线程在改变条件状态之前首先会锁住互斥量。其他线程在获得互斥量之前不会察觉这种改变,因此互斥量锁定后才能计算条件。


和互斥锁一样,使用条件变量,同样首先进行初始化:


int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

和互斥锁的初始化一样,它也可以采用init或者是直接利用宏进行初始化。

条件变量本身就是依赖互斥锁的,条件本身是由互斥量保护的,线程在改变条件状态钱先要锁住互斥量,它是利用线程间共享的全局变量进行同步的一种机制。

我们使用pthread_cond_wait进行等待条件变量变为真,如果在规定的时间不能满足,就会生成一个返回错误码的变量。


 int pthread_cond_wait(pthread_cond_t *restrict cond,
              pthread_mutex_t *restrict mutex);
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

把锁传递给wait函数,函数自动把等待条件的线程挂起,放入消费者等待队列,然后解锁挂起线程所占有的互斥锁,这个时候就可以去跑其他线程,然后当等待条件满足的时候,这个时候从等待队列中出来执行,获得刚才自己所占有的锁。

一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作:
1. 释放Mutex
2. 阻塞等待
3. 当被唤醒时,重新获得Mutex并返回

满足条件的时候可以使用函数pthread_cond_signal进行唤醒

      int pthread_cond_broadcast(pthread_cond_t *cond);
       int pthread_cond_signal(pthread_cond_t *cond);

 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

这两个函数都是用来进行唤醒线程操作的,signal一次从消费者队列中至少唤醒一个线程,broad_cast能唤醒等待该条件的所有线程。

当然和mutex类似,条件变量也需要清除。

int pthread_cond_destroy(pthread_cond_t *cond);
生产者消费者示例:

#include<stdio.h>
#include<assert.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

typedef struct Node
{
	struct Node *next;
	int val;
}Node;

void list_init(Node **phead)
{
	assert(phead);

	Node *temp = (Node*)malloc(sizeof(Node));
	temp->next = NULL;
	temp->val = 0;
	*phead = temp;
}

void list_push(Node *phead,int _data)  //头插
{
	assert(phead);

	Node *pM = (Node*)malloc(sizeof(Node));
	if (pM)
	{
		pM->val = _data;
		pM->next = phead->next;
		phead->next = pM;
	}
}

int empty(Node *phead)
{
	return (phead->next == NULL ? 1: 0);
}

void list_print(Node *phead)
{
	assert(phead);

	if (empty(phead))
		return ;

	Node *pCur = phead->next;
	while (pCur)
	{
		printf("%d->",pCur->val);
		pCur = pCur->next;
	}
	printf("%s\n","NULL");
}

void list_pop(Node *phead,int *data)  //头删
{
	assert(phead);
	
	if (empty(phead))
		return ;

	Node *ptemp = phead->next;

	phead->next = ptemp->next;

	*data = ptemp->val;
	free(ptemp);

	ptemp = NULL;
}

void list_destroy(Node *phead)
{
	assert(phead);

	while (!empty(phead))
	{
		int data;
		list_pop(phead,&data);
	}
}
//生产者线程
void* producer(void *arg)
{
	Node *phead = (Node *)arg;
	while (1)
	{
		pthread_mutex_lock(&mutex);  //申请互斥锁
		int data = rand()%100;
		list_push(phead,data);
		pthread_mutex_unlock(&mutex);//释放互斥锁
		printf("prodecer sucess %d\n",data);
		pthread_cond_signal(&cond); //以单播的方式通知拥有条件变量的另外一个线程,告诉消费者,生产者生产好了,可以消费了。
		sleep(1);
	}

	return NULL;
}

//消费者线程
void* consumer(void *arg)
{
	Node *phead = (Node *)arg;
	while (1)
	{
		int data;

		pthread_mutex_lock(&mutex);
		
		if (empty(phead)) //如果没有资源可以消费了,则等待
		{
			pthread_cond_wait(&cond,&mutex); //这个函数调用一定是在拥有互斥锁的前提下.这个函数做三件事,第一:释放互斥锁,二,阻塞等待,三,唤醒的时候重新获得互斥锁。
		}
		list_pop(phead,&data);//有资源就消费
		pthread_mutex_unlock(&mutex);
		printf("consumer sucess %d\n",data);
	}
	return NULL;
}

int main()
{
	Node *phead;
	list_init(&phead);

	pthread_t id1;
	pthread_t id2;


	pthread_create(&id1,NULL,producer,(void*)phead);
	pthread_create(&id2,NULL,consumer,(void*)phead);


	pthread_join(id1,NULL);
	pthread_join(id2,NULL);


	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	
	return 0;
}

信号量

信号量相关函数:

信号量(Semaphore)Mutex类似,表示可用资源的数量,Mutex不同的是这个数量可以大于1

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

sem_init() 初始化一个定位在 sem 的匿名信号量。value 参数指定信号量的初始值。 pshared 参数指明信号量是由进程内线程共享,还是由进程之间共享。如果 pshared 的值为 0,那么信号量将被进程内的线程共享,并且应该放置在所有线程都可见的地址上(如全局变量,或者堆上动态分配的变量)。如果 pshared 是非零值,那么信号量将在进程之间共享


int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);//类似P操作
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);//类似V操作



用数组模拟环形队列存储数据代码:

#include<stdio.h>
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<semaphore.h>

//二个规则,(1)消费者一定要等到生产才能消费,(2)生产者如果生产的快的话,不能套消费者一个圈

#define SIZE 64

sem_t blanks; //二个信号量
sem_t datas;
int buf[SIZE]; //用数组模拟一个环形队列。


void *productor(void *arg)
{
	int i = 0; //i = 0表示生产的起始位置。
		while (1)
		{
			//	pthread_mutex_lock(&mutex_p);
			sem_wait (&blanks);//生产者在生产之前要有格子资源。
			int data = rand()%1234;
			buf[i] = data;
			printf("productor done...data:%d\n",data);
			i++;
			i%=SIZE;//i++总有超过数组的长度的时候,为了模拟环形队列,所以求模。
			sleep(1); //生产的慢点
			sem_post(&datas);//生产者生产完了,数据资源就多了一个。
			//	pthread_mutex_unlock(&mutex_p);
		}
		return NULL;
}
	
void *consummer(void *arg)
{
	int i = 0;
	while (1)
	{										
	sem_wait(&datas); //消费者在消费之前要有消费资源。
	int data = buf[i];
	printf("consummer done...data:%d\n",data);
	i++;
	i%= SIZE;
	sem_post(&blanks);//消费者消费完了格子资源就多了一个。
	}
	return NULL;
}

	
int main()
{
	sem_init(&blanks,0,SIZE);
	sem_init(&datas,0,0);   //信号量的初始化要在多线程之前

	pthread_t id1,id2;
	pthread_create(&id1,NULL,productor,NULL);	
	pthread_create(&id2,NULL,consummer,NULL);

	pthread_join(id1,NULL);
	pthread_join(id2,NULL);


	sem_destroy(&blanks);
	sem_destroy(&datas);

	return 0;									
}



实验题目: 生产者消费者(综合性实验) 实验环境: C语言编译器 实验内容: ① 由用户指定要产生的进程及其类别,存入进入就绪队列。    ② 调度程序从就绪队列中提取一个就绪进程运行。如果申请的资源被阻塞则进入相应的等待队列,调度程序调度就绪队列中的下一个进程。进程运行结束时,会检查对应的等待队列,激活队列中的进程进入就绪队列。运行结束的进程进入over链表。重复这一过程直至就绪队列为空。    ③ 程序询问是否要继续?如果要转直①开始执行,否则退出程序。 实验目的: 通过实验模拟生产者消费者之间的关系,了解并掌握他们之间的关系及其原理。由此增加对进程同步的问题的了解。 实验要求: 每个进程有一个进程控制块(PCB)表示。进程控制块可以包含如下信息:进程类型标号、进程系统号、进程状态、进程产品(字符)、进程链指针等等。 系统开辟了一个缓冲区,大小由buffersize指定。 程序中有三个链队列,一个链表。一个就绪队列(ready),两个等待队列:生产者等待队列(producer);消费者队列(consumer)。一个链表(over),用于收集已经运行结束的进程 本程序通过函数模拟信号量的操作。 参考书目: 1)徐甲同等编,计算机操作系统教程,西安电子科技大学出版社 2)Andrew S. Tanenbaum著,陈向群,马红兵译. 现代操作系统(第2版). 机械工业出版社 3)Abranham Silberschatz, Peter Baer Galvin, Greg Gagne著. 郑扣根译. 操作系统概念(第2版). 高等教育出版社 4)张尧学编著. 计算机操作系统教程(第2版)习题解答与实验指导. 清华大学出版社 实验报告要求: (1) 每位同学交一份电子版本的实验报告,上传到202.204.125.21服务器中。 (2) 文件名格式为班级、学号加上个人姓名,例如: 电子04-1-040824101**.doc   表示电子04-1班学号为040824101号的**同学的实验报告。 (3) 实验报告内容的开始处要列出实验的目的,实验环境、实验内容等的说明,报告中要附上程序代码,并对实验过程进行说明。 基本数据结构: PCB* readyhead=NULL, * readytail=NULL; // 就绪队列 PCB* consumerhead=NULL, * consumertail=NULL; // 消费者队列 PCB* producerhead=NULL, * producertail=NULL; // 生产者队列 over=(PCB*)malloc(sizeof(PCB)); // over链表 int productnum=0; //产品数量 int full=0, empty=buffersize; // semaphore char buffer[buffersize]; // 缓冲区 int bufferpoint=0; // 缓冲区指针 struct pcb { /* 定义进程控制块PCB */ int flag; // flag=1 denote producer; flag=2 denote consumer; int numlabel; char product; char state; struct pcb * processlink; …… }; processproc( )--- 给PCB分配内存。产生相应的的进程:输入1为生产者进程;输入2为消费者进程,并把这些进程放入就绪队列中。 waitempty( )--- 如果缓冲区满,该进程进入生产者等待队列;linkqueue(exe,&producertail); // 把就绪队列里的进程放入生产者队列的尾部 void signalempty() bool waitfull() void signalfull() void producerrun() void comsuerrun() void main() { processproc(); element=hasElement(readyhead); while(element){ exe=getq(readyhead,&readytail); printf("进程%d申请运行,它是一个",exe->numlabel); exe->flag==1? printf("生产者\n"):printf("消费者\n"); if(exe->flag==1) producerrun();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值