为了处理这种错误,研究人员开发了一些高级语言工具,一种重要的、高级的同步工具,即管程(monitor)。
管程的使用方法
抽象数据类型(简称 ADT)封装了数据及对其操作的一组函数,这一类型独立于任何特定的 ADT 实现。管程类型属于 ADT 类型,提供一组由程序员定义的、在管程内互斥的操作。
管程类型也包括一组变量,用于定义这一类型的实例状态,也包括操作这些变量的函数实现。管程类型的语法如下所示:
monitor monitor name
{
/* shared variable declarations */
function P1 (…) {
…
}
function P2 (…){
…
}
.
.
.
function Pn ( . . . ) {
…
}
initialization_code (…){
…
}
}
管程类型的表示不能直接由各种进程所使用。因此,只有管程内定义的函数才能访问管程内的局部声明的变量和形式参数。类似地,管程的局部变量只能为局部函数所访问。
管程结构确保每次只有一个进程在管程内处于活动状态。因此,程序员不需要明确编写同步约束(图 1)。
图 1 管程的示意图
然而,如到目前为止所定义的管程结构,在处理某些同步问题时,还不够强大。为此,我们需要定义附加的同步机制;这些可由条件(condition)结构来提供。 当程序员需要编写定制的同步方案时,他可定义一个或多个类型为 condition 的变量:
condition x, y;
对于条件变量,只有操作 wait() 和 signal() 可以调用。操作 x.wait();
意味着调用这一操作的进程会被挂起,直到另一进程调用 x.signal();
操作 x.signal()
重新恢复正好一个挂起进程。如果没有挂起进程,那么操作 signal() 就没有作用,即x的状态如同没有执行任何操作(图 2 )。这一操作与信号量的操作 signal() 不同,后者始终影响信号量的状态。
图 2 具有条件变量的管程
现在,假设当操作 x.signal()
被一个进程 P 调用时,在条件变量 x 上有一个挂起进程 Q。显然,如果挂起进程 Q 允许重执行,那么进程 P 必须等待。否则,管程内有两个进程 P 和 Q 可能同时执行。
注意,从概念上说两个进程都可以继续执行。有两种可能性存在:
- 唤醒并等待:进程 P 等待直到 Q 离开管程,或者等待另一个条件。
- 唤醒并继续:进程 Q 等待直到 P 离开管程或者等待另一个条件。
对于任一选项,都有赞同理由。一方面,由于 P 已经在管程中执行,唤醒并继续的方法似乎更为合理。另一方面,如果我们允许线程 P 继续,那么 Q 等待的逻辑条件在 Q 重新启动时可能已不再成立。Concurrent Pascal 语言采用这两种选择的折中。当进程 P 执行操作 signal 时,它立即离开管程。因此,进程 Q 立即重新执行。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/21982.html