生产者消费者问题
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。 生产者,消费者共享一个初始化为空,大小为n 的缓冲区。 如何使用信号量机制(P、V操作)实现生产者、消费者进程的这些功能呢? 信号量机制可实现互斥、同步、对一类系统资源的申请和释放。
对于信号量可以看这篇信号量机制
使用信号量解决生产者消费者问题
生产者、消费者共享一个初始为空、大小为n的缓冲区。只有缓冲区没满的时候,生产者才能把产品放入缓冲区,否则必须等待。只有缓冲区不为空的时候,消费者才能从中取出产品,否则必须等待。
缓冲区是临界资源,各个进程必须互斥访问。
首先定义信号量;
semaphore mutex=1; //互斥信号量,实现对缓冲区的互斥访问
semaphore empty=n; //同步信号量,表示空闲缓冲区的数量
semaphore full = 0; //同步信号量,表示产品数量,也是非空缓冲区的数量
producer(){
while(1){
生产一个产品;
p(empty); //小号一个空闲缓冲区
p(mutex); // 实现互斥是在同一进程中进行PV操作
把产品放入缓冲区;
V(mutex);
V(full); //增加一个产品
}
}
// 实现两个进程的同步,是在其中一个进程中执行P,另一个进程中执行V。
consumer(){
while(1){
P(full); //消耗一个产品
P(mutex);
从缓冲区取出一个产品
V(mutex);
V(empty); //增加一个空闲缓冲区
使用产品;
}
}
管程
为啥要引入管程?
信号量机制存在问题:编写程序困难、易出错。能不能设计一种机制,让程序员写程序时不需要再关注复杂的PV操作,让写代码更加轻松呢?
1973年, Brinch Hansen首次在程序设计语言( Pascal中引入了“管程”成分一一一种高级同步机制)
管程的定义和基本特征
定义
管程是一种特殊的软件模块,由这些部分组成:
- 局部于管程的共享数据结构说明;
- 对该数据结构进行操作的一组过程(函数)
- 对局部于管程的共享数据设置初始值的语句
- 管程自己的名字
特征
- 管程内的数据只能被管程中定义的函数(过程)访问;(类比于java实体类的get方法)
- 一个进程只能通过调用管程内的过称(函数/方法)才能进入管程访问共享数据(进程.getXxx())
- 每次仅仅允许一个进程在管程内自行某个内部的过程
用管程解决生产者消费者问题
定义一个管程以下是伪代码
monitor ProducterConsumer
condition full,empty; //条件变量用来实现同步(排队)
int count=0; // 缓冲区中的产品数
void insert(Item item){
//把产品item放入缓冲区
if(count == N)
wait(full);
count++;
insert_item(item);
if(count == 1)
signal(empty);
}
Item remove(){ //从缓冲区取出一个产品
if(count==0)
wait(empty);
count--;
if(count == N-1)
signal(full);
return remove_item();
}
end monitor;
生产者进程
producter(){
while(1){
item = 生产一个产品;
ProducterConsumer.insert(item);
}
}
消费者进程
consumer(){
while(1){
item = ProducterConsumer.remove();
消费产品;
}
}
引入管程的目的无非就是更方便地实现进程的互斥与同步
- 需要在管程中定义共享数据
- 需要在管程中定义用于访问这些共享数据的“入口”–其实就是一些函数
- 只有通过这些函数才能访问管程中的共享数据
- 管程中有很多接口,但是每次只能开放其中一个入口,并且只能让一个进程或线程进入,(互斥的特性交给了编译器去实现,程序员不用关心)
- 可在管程中设置条件变量级等待/唤醒操作以解决同步问题。
通过管程提供特定的接口来实现进程的同步与互斥,这就是一种封装的思想。