管程

参考一

3.8 管程的概念

3.8.1 管程的引入
 
  信号量机制是解决进程互斥、同步的有效工具,但前提是信号量设置、其初值的确定以及相关进程中安排P-V操作的位置必须正确,否则同样也会造成与时间有关的错误,有时甚至造成死锁。Dijkstra于1971年提出,把所有进程对某一临界资源互斥、同步操作都集中起来,构成所谓的"秘书"进程。1975年,Hansen和Hoare又把"秘书"进程思想发展为管程概念,把并发进程间的互斥、同步操作,分别集中于相应的管程中

3.8.2 管程的定义

管程的定义

  Hansen为管程所下的定义是:"一个管程定义了一个数据结构和能为并发进程调用的在该数据结构上的一组操作过程,这组操作过程能同步进程和改变管程中的数据。"

管程的组成

  管程由三个组成部分:

 (1)局部于管程的共享变量说明。

 (2)对该数据结构进行操作的一组互斥执行的过程。

 (3)对局部于管程的数据设置初始值等语句。

3.8.3 实现管程的三个关键问题

  实现管程时必须考虑三个关键问题,即互斥、同步和条件变量。

1.互斥

  管程必须能有效地实现互斥以达到进程互斥访问管程中定义的数据结构。当进程需要访问管程中的临界资源的时候,可调用管程中的有关入口过程。但当几个进程都需调用某一管程的同一个或不同的入口过程时,仅允许一个进程调用进入管程,而其他调用者必须等待,即诸进程必须互斥地调用执行管程入口过程。

2.同步

  在管程中必须设置两个同步操作原语wait和signal。当进程通过管程请求访问共享数据而未能满足时,管程便调用wait原语使该进程阻塞等待,当另一进程访问完该共享数据且释放后,管程便调用signal原语,唤醒等待队列中的队首进程。

3.条件变量

  为了区别等待的不同原因,管程又引入了条件变量。不同的条件变量,对应不同原因的进程阻塞等待队列,初始时为空。条件变量上能作wait和signal原语操作,若条件变量名为nonbusy,则调用同步原语的形式为wait(nonbusy)和signal(nonbusy)。

 

参考二

管程

一、信号量的缺点

信号量的使用一定要小心,如下图中解决生产者-消费者问题的程序:

管程_第1张图片

如果在producer的执行函数中,将empty与mutex的down操作互换,如果此时mutex为0,将首先对mutex进行down操作,进程陷入阻塞,而同时,当consumer的执行函数执行到down(&mutex)的时候,由于mutex为0,因此,consumer线程也将进入阻塞,两个进程都将永远进入阻塞状态,这被称为“死锁”

这说明使用信号量时一定要非常小心,一处很小的错误将有可能导致很大的麻烦,因为竞争条件、死锁以及其他一些问题都是不可预测和不可再现的行为


为了更易于编写正确的程序,一种高级同步原语 -- 管程(monitor)诞生了


二、管程

一个管程是一个由过程、变量及数据结构等组成的一个集合,它们组成一个特殊的模块或软件包。

  1. 管程内部的共享变量
  2. 管程内部的条件变量
  3. 管程内部并行执行的进程
  4. 对局部于管程内部的共享数据设置初始值的语句

进程可以在任何需要的时候调用管程中的过程,但是他们不能在管程之外声明的过程中直接访问管程内的数据结构

但是,需要注意的是管程是语言概念,而C语言并不支持它

任意时刻,管程中只能有一个活跃的进程,这一特性是的管程能够有效地完成互斥,由编译器选择采取与其他过程调用不同的方法来处理对管程的调用

典型的处理方法是,当一个进程调用管程过程时,该过程中的前几条指令将检查在管程中是否有其他的活跃进程,如果有,调用进程将被挂起,知道另一个进程离开管程将其唤醒,如果没有,则该调用进程可以进入

进入管程时的互斥由编译器负责,但通常的做法是用一个互斥量或二元信号量,因为是有编译器而非程序员来安排互斥,所以出错的可能性要小得多

在任何一个时刻,写管程的人无需关心编译器是如何实现互斥的,他只需要知道将所有的临界区转换成管程过程即可,绝不会有两个进程同时执行临界区中的代码。


三、管程中的条件变量

管程提供了一种实现互斥的渐变途径,但是我们还需要一种办法使得进程在无法继续运行时被阻塞,解决这个问题的方法就是引入条件变量,以及相关的两个操作:wait和signal,当一个管程过程发现他无法继续运行时(如生产者发现缓冲区已满),他会在某个条件变量上(如full)上执行wait操作,该操作导致调用进程自身阻塞,并将另一个等在管程外的进程调入管程,同时,一个进程也可以通过对伙伴正在等待的一个条件变量执行signal操作完成唤醒正在睡眠的伙伴进程,但是这个时候就有可能会出现两个活跃进程同时处在管程中的情况,这个情况有两种方案可以解决:

  1. 运行新唤醒的进程,挂起之前的进程

  2. 执行signal的进程立即退出管程,即规定signal只能作为管程过程的最后一条语句

  3. 让发信者继续运行直到发信者退出管程后才让被唤醒的进程运行

很明显,第一种方案更加简单,所以一般我们采取这一方案


注意,条件变量与信号量不同,他并不是计数器,如果向一个条件变量发送信号,而这个条件变量上此时并没有等待进程,则这个信号就会丢失,也就是说wait必须在signal之前执行

wait、signal与sleep、wakeup最大的区别是他们不存在严重的竞争条件,因为可能存在一种情况,即当一个进程正要去sleep而实际还没有sleep的时候,另一个进程企图唤醒他,从而造成了wakeup信号的丢失,而管程中不会存在这样的问题,因为在缓冲区满,生产者wait前,消费者进程根本不可能进入管程


四、代码示例

管程_第2张图片

如图所示,是用管程实现生产者-消费者问题揭发框架的一个类似于pascal的伪代码


java支持用户级进程,只要将关键词synchronized加入到方法声明中,java就保证这个方法一旦被某个进程执行,就不允许其他进程执行它,因此我们可以通过这一特性实现管程的编程

管程_第3张图片

管程_第4张图片

这个例子其实并不难懂,由于有synchronized关键字,所以无论是producer要进行的insert方法还是consumer要进行的remove方法都只能让一个进程进入,因此他们不需要再担心竞争条件

java中并没有条件变量,反之,java提供了两个过程:wait和notify,与sleep和wakeup等价,但他们并不受竞争条件约束,而是通过异常机制实现对中断情况的处理

 

参考三

管程的引入

信号量机制的引入解决了 进程同步的描述问题,但信号量的大量同步操作分散在各个进程中不便于管理,还有可能导致系统死锁。如:生产者消费者问题中将P、V颠倒可能死锁。
为此Dijkstra于1971年提出:把所有进程对某一种 临界资源的同步操作都集中起来,构成一个所谓的秘书进程。凡要访问该 临界资源的进程,都需先报告秘书,由秘书来实现诸进程对同一临界资源的互斥使用。

管程概念

Hansan为管程所下的定义是:管程定义了一个 数据结构和能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。

管程的构成

局部于管程的共享 数据结构
对共享数据结构进行操作的一组函数
对局部于管程的数据设置初始值的语句

管程的语法

Monitor monitor _name;/*管程名*/
variable declarations; /*共享 变量说明*/
void Entry P1(...) /*对 数据结构操作的函数*/
{ … }
void Entry P2(...)
{ … }
void Entry Pn(...)
{ … }
{
initialization code; /*设初值语句*/
}

管程的基本特性

局部于管程的数据只能被局部于管程内的函数所访问。
一个进程只有通过调用管程内的函数才能进入管程访问共享数据。
每次仅允许一个进程在管程内执行某个函数。
由于管程是一个语言成分,所以管程的互斥访问完全由 编译程序在编译时自动添加上,无需程序员关心,而且保证正确。

条件变量

利用管程实现同步时,还应设置条件变量和在条件变量上进行操作的两个 同步原语。
条件变量用于区别各种不同的等待原因。其说明形式为: condition : x,y;
同步原语wait和signal。wait使调用进程等待,并将它排在相应的等待队列上;signal唤醒等待队列的队首进程。使用方式为:x.wait,x.signal。

 

你可能感兴趣的:(管程)