管程的出现是一种进程同步工具。管程的特性保持了进程互斥,无须程序员自己实现互斥,从而降低了死锁发生的可能性。同时管程提供了条件变量,可以让程序员灵活地实现进程同步。
管程有点类似于在面向对象时候学习到的类。
管程有四个部分组成:
①管程的名字
②局部于管程内部的共享结构数据说明;
③对该数据结构进行操作的一组过程(可以理解为函数)
④对局部于管程内部的共享数据设置初始值的语句。
可能通过文字来看不是很明白,下面举个生产者消费者的例子(生产者消费者是经典的进程同步问题,这里只做简述,详细的内容可以移步另一篇文章。一组生产者和一组消费者共享一个初始为空,大小为n的缓冲区,只有缓冲区没满时,生产者才能放东西,否则等待;只有缓冲区不空即有东西时,消费者才能取,否则等待。注:缓冲区是临界资源):
-
monitor ProducerConsumer{
-
condition full,empty;//条件变量用来实现同步(排队) 条件变量在下面有解释
-
int count=0;//缓冲区中的产品数
-
void insert(Item item){//把产品item放入缓冲区
-
if(count==N)//如果缓冲区满了
-
wait(full);
-
count++;
-
insert_item(item);
-
if(count==1)//一开始count是0,加1后就相当于有东西可以取了,那么就唤醒empty
-
signal(empty);
-
}
-
Item remove(){//从缓冲区取出一个产品
-
if(count==0)//没产品
-
wait(empty);//阻塞
-
count--;
-
if(count==N-1)//count--是N-1的话说明刚才是满的,那么之前就不能插入,现在不满了就可以唤醒full继续插入
-
signal(full);
-
return remove_item();
-
}
-
}
由编译器负责实现各进程互斥地进入管程中的过程。管程中设置条件变量和等待/唤醒操作,以解决进程同步问题。
管程的基本特征:
①局部管程的数据只能被局部于管程的过程所访问。
②一个进程只有通过调用管程内的过程才能进入管程访问共享数据。
③每次仅允许一个进程在管程内执行某个内部过程。
条件变量
当一个进程进入管程后被阻塞,直到阻塞的原因解除时,在此期间,如果该进程不释放管程,那么其他进程无法进入管程。为此,将阻塞原因定义为条件变量(condition)。通常一个进程阻塞的原因不止一个,所以要设置很多条件变量。每个条件变量保存了一个等待队列,用于记录因该条件变量而阻塞的所有进程,对条件变量只有两种操作,就是wait和signal。
比较条件变量和信号量:
相似点:条件变量的wait/signal操作类似信号量P/V操作,可以实现进程的阻塞/唤醒。
不同点:条件变量是“没有值”的,仅实现了“排队等待”功能,而信号量是“有值”的,信号量的值反映了剩余资源数,而在管程中,剩余资源数用共享数据结构记录。
最后,总结一下引入管程。
引入管程的目的无非就是要更方便地实现进程互斥和同步
-
需要在管程中定义共享数据(如生产者消费者中的缓存区)
-
需要在管程中定义用于访问这些共享数据的“入口”——即函数
-
只有通过这些特定的“入口”才能访问共享数据
-
管程中有很多“入口”,但是每次只能开放其中一个“入口”,并且只能让一个进程或线程进入。注意:这种互斥是由编译器负责实现的,程序员不用关心。
-
可在管程中设置条件变量及“等待/唤醒操作”以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,该进程线释放管程的使用权,也就是让出“出口”);可以通过唤醒操作将等待在条件变量上的进程或线程唤醒。