操作系统第二章---信号量机制
- 信号量机制
- 整型信号量
- 记录型信号量
- 知识点回顾
- 信号量机制实现进程互斥
- 信号量机制实现进程同步
- 复杂关系中的实现
- 知识点回顾
- 经典问题解析
- 生产者消费者问题
- 多生产者和多消费者问题
- 吸烟者问题
- 读者-写者问题
- 知识点回顾
- 哲学家就餐问题
- 回顾知识点
- 管程
- 知识点回顾
信号量机制
我们可以发现不管是软件还是硬件都有一定的缺点:
1.软件实现方式中的:单标志法、双标志先检查、双标志后检查三种算法中,都是因为检查和上锁的两个操作不是一气呵成的,从而导致两个进程有可能同时进入临界区
2.不管是软件还是硬件都没有实现让权等待
因此,迪杰斯特拉(Dijkstra,这男人真牛逼,哪里都有他)提出了信号量机制
用户进程可以通过使用操作系统提供的一-对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。
信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量;因此信号量就有两种:整形型号量和记录型型号量),可以用一个信号量来表示,系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为1的信号量。
原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。软件解决方案的主要问题是由“进入区的各种操作无法一气呵成”,因此如果能把进入区、退出区的操作都用“原语”实现,使这些操作能“一气呵成”就能避免问题。
一对原语: wait(S) 原语和signal(S)原语,可以把原语理解为我们自己写的函数,函数名分别为wait和signal,括号里的信号量S其实就是函数调用时传入的一个参数。
wait、signal 原语常简称为P、V操作( 来自荷兰语proberen和verhogen )。因此,做题的时候常把wait(S)、signal(S) 两个操作分别写为P(S)、V(S)
整型信号量
用一个整数型的变量作为信号量,用来表示系统中某种资源的数量。(与普通整数变量的区别:对信号量的操作只有三种,即初始化、P操作、V操作)
Eg:某计算机系统中有一台打印机.
int S = 1; // 初始化整型信号量s,表示当前系统中可用的打印机资源数
void wait (int S) { //wait 原语,相当于“进入区”
while (S <= 0);//如果资源数不够,就一直循环等待
S=S-1;//如果资源数够,则占用一个资源
}
void signal (int S) { //signal 原语,相当于”退出区"
S=S+1;//使用完资源后,在退出区释放资源
}
进程P0:
wait(S);
//进入区,申请资源
使用打印机资源... //临界区, 访问资源
signal(S);
//退出区,释放资源
进程P1:
wait(S);
使用打印机资源...
signal(S);
因此如果P0用了,即使切换到了P1,资源数也是0,一直在wait中等待
存在的问题:不满足“让权等待”原则,会发生“忙等”
记录型信号量
整型信号量的缺陷是存在“忙等”问题,因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量。
/*记录型信号的定义*/
typedef struct {
int va Lue;//剩余资源数
struct process *L; // 等待队列
} semaphore;
/*某进程需要使用资源时,通过wait原语申请*/
void wait (semaphore S) {
S.value--;
if (S.value <0 ) {
block (S.L);
}
}
//block()函数:如果剩余资源数不够,使用block原语使进程从运行态进入阻塞态,并把挂到信号量S的等待队列(即阻塞队列)中
/*进程使用完资源后,通过signal原语释放*/
void signal (semaphore S) {
s. value++;
if (S.value <= 0) {
wakeup(S.L);
}
}
//wakeup()函数:释放资源后,若还有别的进程在等待这种资源,则使用wakeup原语唤醒等待队列中的一个进程,该进程从阳塞态变为就绪态
简单的说其实就是有一个进来,如果有资源就给他,没资源就给他变成阻塞态,然后挂出去;当释放了一个,并且资源数仍然小于0(因为连等待中的也记录了),就会唤醒一个进程来使用
个人感觉挺好理解,就不想举例子了,如果字面无法理解,就去看看操作系统2.3.4的第16分钟开始看
对信号量S的一次P操作意味着进程请求一个单位的该类资源,因此需要执行S.value–,表示资源数减1,当S.value < 0时表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞(当前运行的进程从运行态→阻塞态),主动放弃处理机,并插入该类资源的等待队列S.L中。可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。
对信号量S的一次V操作意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数加1,若加1后仍是S.value<= 0,表示依然有进程在等待该类资源,因此应调用wakeup原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态>就绪态)
知识点回顾
注:若考试中出现P(S)、V(S) 的操作,除非特别说明,否则默认S为记录型信号量。
信号量机制实现进程互斥
1.分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
2.设置互斥信号量mutex,初值为1
3.在临界区之前执行P(mutex)
4.在临界区之后执行V(mutex)
/*信号机制实现互斥*/
semaphore mutex=1; // 初始化信号量(没有特殊情况,为了方便可以这么写)
P1(){
P(mutex);//使用临界资源前需要加锁
临界区代码段...
V(mutex);//使用临界资源后需要解锁
}
P2(){
P (mutex) ;
临界区代码段...
V(mutex);
}
这里就是P1调用了资源,给他锁上了,别的就访问不了了,然后执行完毕必须释放了,P2才能调用
注意:对不同的临界资源需要设置不同的互斥信号量。
P、V操作必须成对出现。缺少P(mutex)就不能保证临界资源的互斥访问。缺少V(mutex)会导致资源永不被释放,等待进程永不被唤醒。
信号量机制实现进程同步
进程同步:要让各并发进程按要求有序地推进。
比如,P1、 P2 并发执行,由于存在异步性,因此二者交替推进的次序是不确定的。
若P2的“代码4”要基于P1的“代码1”和“代码2”的运行结果才能执行,那么我们就必须保证“代码4”一定是在 “代码2”之后才会执行。
这就是进程同步问题,让本来异步并发的进程互相配合,有序推进。
实现方式:
1.分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作( 或两句代码)
2.设置同步信号量S,初始为0
3. 在“前操作”之后执行V(S)
4.在“后操作”之前执行P(S)
这个东西不是很好理解,直接举例子
/*信号量机制实现同步*/
semaphore S=0; // 初始化同步信号量,初始值为0
代码4必须得在代码1和代码2后面工作,因此在代码1和代码2执行以后才释放;在代码4前面进行P操作,防止在没有释放以前执行
官方推导:
若先执行到V(S)操作,则S++后S=1。 之后当执行到P(S)操作时,由于S=1,表示有可用资源,会执行S- -,S的值变回0,P2进程不会执行block原语,而是继续往下执行代码4。
若先执行到P(S)操作,由于S=0, S-- 后S=-1,表示此时没有可用资源,因此P操作中会执行block原语,主动请求阻塞。之后当执行完代码2,继而执行V(S)操作,S++, 使S变回0,由于此时有进程在该信号量对应的阻塞队列中,因此会在V操作中执行wakeup原语,唤醒P2进程。这样P2就可以继续执行代码4了
复杂关系中的实现
进程P1中有句代码S1,P2中有句代码S2…P6中有句代码S6。这些代码要求按如下前驱图所示的顺序来执行:
解读图片:
S2和S3必须在S1之后
S4和S5必须在S2之后
S6必须在S4、S5、S3之后因此,如果上述的算法,我们需要为每一个关系都配置一个同步变量
S1-S2:a
S1-S3:b
S2-S4:c
S2-S5:d
S3-S6:e
S4-S6:f
S5-S6:g
那不就是这样子么
知识点回顾
经典问题解析
生产者消费者问题
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(注: 这里的“产品”理解为某种数据)
生产者、消费者共享一个初始为空、大小为n的缓冲区。
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
缓冲区是临界资源,各进程必须互斥地访问。
题目就是
如何用信号量机制(P、V操作)实现生产者、消费者进程的这些功能呢?
信号量机制可实现互斥(设置一个初值为1的互斥信号量)、同步(设置一个初值为0的同步信号量)、对一类系统资源的申请和释放。
步骤:
1.分析关系。找出题目中描述的各个进程,分析它们之间的同步、斥关系。
这题中,缓冲区没满的时候,生产者才能放入(一前一后):同步关系;缓冲区不空的时候,消费者才能取出(一前一后):同步关系;缓冲区一次只有一个进程可以访问:互斥关系
2.整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
这题中,生产者放入是一个V1操作(释放一个表示缓冲区有数据的信号量),P2操作(消耗一个代表缓冲区无数据的信号量);消费者取出是一个P1操作(消耗一个表示缓冲区有数据的信号量),V2操作(释放一个表示缓冲区无数据的信号量);还有一个就是访问的时候有一个代表互斥的信号量
可能有点绕,需要稍微的理解一下下
3.设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。 (互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少,这里是缓冲区的大小n)
semaphore mutex = 1; //互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; //同步信号量,表示空闲缓冲区的数量
semaphore full = 0; //同步信号量,表示产品的数量,也即非空缓冲区的数量
可以发现:实现互斥的信号量实在同一个进程实现的,实现同步的信号量是在两个进程之间实现的
这里如果互斥信号量的P放在前面会导致“死锁”的情况
若此时缓冲区内已经放满产品,则empty=0, full=n。
则生产者进程执行①使mutex变为0,再执行②,由于已没有空闲缓冲区,因此生产者被阻塞。
由于生产者阻塞,因此切换回消费者进程。消费者进程执行③,由于mutex为0,即生产者还没释放对临界资源的“锁”,因此消费者也被阻塞。
这就造成了生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的情况,生产者和消费者循环等待被对方唤醒,出现“死锁”
同样的,若缓冲区中没有产品,即full=0,empty=n。 按③④①的顺序执行就会发生死锁。
因此,实现互斥的P操作一定要在实现同步的P操作之后
多生产者和多消费者问题
桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。用PV操作实现上述过程。
(这里为啥叫多生产者多消费者呢,因为生产者进程各自生产的东西不一样,消费者消费的东西也不一样)
1.关系分析
我自己分析:生产者进程是两个,消费者进程是两个,缓冲区大小为1;应该有一个盘子内是否放了水果的信号量(同步信号量),盘子内苹果数量(同步信号量),盘子内橘子数量(同步信号量),盘子是否正在被使用(互斥信号量)
我分析的非常正确
互斥关系:(mutex= 1)
对缓冲区(盘子)的访问要互斥地进行
同步关系(一前一后) :
(1)父亲将苹果放入盘子后,女儿才能取苹果(apple=0)
(2)母亲将橘子放入盘子后,儿子才能取橘子(orange=0)
(3)只有盘子为空时,父亲或母亲才能放入水果(plate=1)
2.整理思路
父亲放入苹果,V(apple),女儿取走苹果P(apple)
母亲放入橘子,V(orange),儿子取走橘子P(orange)
盘子中的东西被取走V(plate),盘子被放入东西P(plate)
3.设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
semaphore mutex = 1 ; //实现互斥访问盘子(缓冲区)
semaphore apple = 0; //盘子中有几个苹果
semaphore orange = 0; //盘子中有几个橘子
semaphore plate = 1; //盘子中还可以放多少个水果
dad(){
while(1) {
准备一个苹果;
P(plate);
P(mutex);
把苹果放入盘子;
V(mutex);
V(app1e);
}
}
mom(){
while(1) {
准备一个橘子;
P(plate);
P(mutex);
把橘子放入盘子;
V(mutex);
V(orange);
}
}
daughter(){
while (1) {
P(apple);
P(mutex};
从盘中取出苹果;
V(mutex);
V(plate);
吃掉苹果;
}
}
son(){
while(1) {
P(orange);
P(mutex);
从盘中取出橘子;
V(mutex);
V(plate);
吃掉橘子;
}
}
注:这种情况中是可以不专门设置mutex(互斥信号量),因为缓冲区大小只有一个,要向访问每次都只有一个进程可以进行。但是如果缓冲区大小大于等于2则会发生同时访问的情况。
吸烟者问题
假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟, 抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了, 供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)
这个题目本质是一个单生产者多消费者问题(并且注意题目中要求是三个人轮流抽烟)
1.关系分析
生产者进程1个,消费者进程3个,缓冲区大小为1(虽然放的是两种,但是每次都是一个进程需要的,所以可以视为1);有一个互斥信号量,有一个纸+胶水信号量(同步关系,消费者1需求),有一个烟草+胶水信号量(同步关系,消费者2需求),有一个烟草+纸信号量(同步关系,消费者3需求),桌子上有东西信号量(同步关系)
2.整理思路
互斥就不说了(但是这里缓冲区只有1,根据多生产者多消费者模型说的,我们就不设置互斥信号量了)
生产者放入纸+胶水V(offer1),消费者1取走P(offer1)
生产者放入烟草+胶水(offer2),消费者2取走P(offer2)
生产者放入烟草+纸V(offer3),消费者3取走P(offer3)
生产者放入材料P(finish),消费者取走材料V(finish)
3.设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。 (互斥信号量初值一-般为1,同步信号量的初始值要看对应资源的初始值是多少)
semaphore offerl = 0;//桌上组合- -的数量
semaphore offer2 = 0;//桌上组合二的数量
semaphore offer3 = 0;//桌上组合三的数量
semaphore finish = 0;//抽烟是否完成
int i = 0; //用于实现三个抽烟者轮流抽烟
provider () {
while (1) {
if(i == 0){
将组合一放桌上;
V(offer1);
}
else if(i == 1){
将组合二放桌上;
V(offer2);
}
else if(i == 2){
将组合三放桌上;
V(offer3);
}
i = (i+3)%3;
P(finish);
}
smoker1(){
while(1){
P(offer1) ;
从桌上拿走组合一;
卷烟;
抽掉;
V(finish) ;
}
}
smoker2(){
while(1){
P(offer2) ;
从桌上拿走组合二;
卷烟;
抽掉;
V(finish) ;
}
}
smoker3(){
while(1){
P(offer3) ;
从桌上拿走组合三;
卷烟;
抽掉;
V(finish) ;
}
}
这个需要理解一下下,但是还是可以理解的,主要还是对PV操作的理解
注:这里轮流抽烟的实现方式就是设定了一个标号,每次是这个号码的人进行抽烟,标号是轮流的;并且注意,每次那个V都要紧跟着事件的完成
读者-写者问题
有读者和写者两组并发进程,共享-一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作:②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。
1.关系分析
写者进程n,读者进程m,缓冲区大小x;写者与任何进程之间都互斥(互斥关系rw),写者开始写之前,应该是只有他自己(同步关系)
2.整理思路
写者进行工作之前P(rw),写完以后V(rw)
读者进行读取之前P(rw),读完以后V(rw)(但是这样无法实现读进程可以同时进行,因此就出现了一个计数变量count,count>0的时候上锁,count=0的时候解锁)
3.设置信号量。设置需要的信号量,开根据题日条件确定信号量初值。 (互斥信号重初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
semaphore rw=1;//用于实现对文件的互斥访问。表示当前是否有进程在访问共享文件
int count = 0;//记录当前有几个读进程在访问文件
semaphore mutex = 1; //用于保证对count变量的互斥访问
writer(){
while (1) {
P(rw);
写文件...
V(rw);
}
}
reader () {
while(1) {
/*
count++;
if(count==1){
P(rw);
}
个人感觉这个方法也没有问题,但是官方给出的代码是下面的形式,上面是我自己写的
*/
P(mutex);
if(count == 0){
P(rw);
}
count++;
V(mutex);
读文件...
P(mutex);
count--;
if(count == 0){
V(rw);
}
V(mutex);
}
}
为什么要加mutex呢,防止在P(rw)了以后切换进程,导致后面阻塞,这个前面已经提出过这个问题,就不在过多赘述
注:但是这个还是会又有一些问题,如果读进程一直进来,就会导致写进程饿死(因此需要一个优先级调度,写进程来了读进程就得让路)
写优先如何实现呢:
我们再加入一个用于实现写优先的信号量
semaphore rw=1;//用于实现对文件的互斥访问。表示当前是否有进程在访问共享文件
int count = 0;//记录当前有几个读进程在访问文件
semaphore mutex = 1; //用于保证对count变量的互斥访问
semaphore w = 1;//用于实现“写优先”
writer(){
while (1) {
P(w);
P(rw);
写文件...
V(rw);
V(w);
}
}
reader () {
while(1) {
/*
count++;
if(count==1){
P(rw);
}
个人感觉这个方法也没有问题,但是官方给出的代码是下面的形式,上面是我自己写的
*/
P(w)
P(mutex);
if(count == 0){
P(rw);
}
count++;
V(mutex);
V(w);
读文件...
P(mutex);
count--;
if(count == 0){
V(rw);
}
V(mutex);
}
}
这里直接通过手推过程来解释吧
(1)读者→读者
因为读者进程在读文件就V(w)了,所以不影响
(2)写者→写者
因为写者进程是在最后才V(w),所以第二个会阻塞在P(w)
(3)写者→读者
同样是因为写者进程最后才V(w),所以读者进程会阻塞在P(w)
(4)读者→写者→读者
这个就是写优先体现的具体案例了
虽然一开始是读者进程,会在读文件之前就V(w),然后并发时执行写者进程的P(w),但是写者进程会阻塞在P(rw),后面的读者进程会阻塞在P(w),会等前面的读完,写者进程开始,写完,后面的读者进程才能继续工作
(5)写者→读者→写者
这个就是一样的了,都会被写者进程的P(w)阻塞
结论:在这种算法中,连续进入的多个读者可以同时读文件:写者和其他进程不能同时访问文件:写者不会饥饿,但也并不是真正的“写优先”,而是相对公平的先来先服务原则。有的书上把这种算法称为“读写公平法”
知识点回顾
读者写者问题为我们解决复杂的互斥问题提供了一个参考思路。
其核心思想在于设置了一个计数器count用来记录当前正在访问共享文件的读进程数。我们可以用count的值来判断当前进入的进程是否是第一个/最后一个读进程,从而做出不同的处理。
另外,对count变量的检查和赋值不能一-气呵成导致了一些错误,如果需要实现“一气呵成”,自然应该想到用互斥信号量。
最后,还要认真体会我们是如何解决“写进程饥饿”问题的。
绝大多数的考研PV操作大题都可以用之前介绍的几种生产者消费者问题的思想来解决,如果遇到更复杂的问题,可以想想能否用读者写者问题的这几个思想来解决。
哲学家就餐问题
一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子, 桌子的中间是一一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(-一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
1.关系分析
每个筷子之间的相邻哲学家都对访问筷子是互斥的
2.整理思路
这里只有互斥关系,并且每个哲学家必须有两根筷子才能进食(但是,如果每个哲学家都拿着自己右边的筷子,就会出现“死锁”现象)
3.信号量设置。定义互斥信号量数组chopstick[5]={1,1,1,1,1}用于实现对5个筷子的互斥访问。并对哲学家按0~4编号,哲学家左边的筷子编号为i,右边的筷子编号为(i+1)%5。
如何防止死锁的发生呢:
(1)增加限制条件,即每次最多只能有四个哲学家同时进食
(2)要求奇数号的哲学家必须先拿左边的,偶数号的哲学家必须先拿右边的
(3)设置一个互斥信号量,类似于原语的关中断和开中断嘛,让他一气呵成,不被打断(但是这个操作会导致除他以外所有的哲学家即使有两只筷子也不能就餐;即每次只有一个哲学家可以进食,但是我觉得如果n大了以后还是出现多个人的)
这个就不用解释吧,自己思考一下就能想通
回顾知识点
哲学家进餐问题的关键在于解决进程死锁。
这些进程之间只存在互斥关系,但是与之前接触到的互斥关系不同的是,每个进程都需要同时持有两个临界资源,因此就有“死锁"问题的隐患。
如果在考试中遇到了一个进程需要同时持有多个临界资源的情况,应该参考哲学家问题的思想,分析题中给出的进程之间是否会发生循环等待,是否会发生死锁。
可以参考哲学家就餐问题解决死锁的三种思路。
管程
知识点框架
为什么要引入管程呢(因为懒!用PV操作编写程序太麻烦了!)
因此1973年,Brinch Hansen(管他啥人呢)引入了管程,一种高级同步机制
管程是一种特殊的软件模块(有那么点点的像类),有这些部分组成:
1.局部于管程的共享数据结构说明;
2.对该数据结构进行操作的一组过程;
3.对局部于管程的共享数据设置初始值的语句;
4.管程有一个名字。
管程的基本特征:
1.局部于管程的数据只能被局部于管程的过程所访问;(这个就是类的pravite属性)
2.一个进程只有通过调用管程内的过程才能进入管程访问共享数据;(这个也是)
3.每次仅允许一个进程在管程内执行某个内部过程。(就是类似于一个临界资源,互斥使用)
引入管程的目的无非就是要更方便地实现进程互斥和同步。
1.需要在管程中定义共享数据(如生产者消费者问题的缓冲区)
2.需要在管程中定义用于访问这些共享数据的“入口”–其实就是一些函数(如生产者消费者问题中,可以定义一个函数用于将产品放入缓冲区,再定义一个函数用于从缓冲区取出产品)
3.只有通过这些特定的“入口”才能访问共享数据
4.管程中有很多“入口”,但是每次只能开放其中一个“入口”,并且只能让一个进程或线程进入(如生产者消费者问题中,各进程需要互斥地访问共享缓冲区。管程的这种特性即可保证一个时间段内最多只会有一个进程在访问缓冲区。注意:这种互斥特性是由编译器负责实现的,
程序员不用关心)
5.可在管程中设置条件变量及等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,该进程应先释放管程的使用权,也就是让出“入口”) ;可以通过唤醒操作将等待在条件变量上的进程或线程唤醒。
程序员可以用某种特殊的语法定义一个管程 (比如: monitor ProducerConsumer … end monitor; )之后其他程序员就可以使用这个管程提供的特定“入口”很方便地使用实现进程同步/互斥了。(说实话我只看出来这是把同步和互斥封装成了一个类,然后直接调用就可以,其实就是这么个东西)
知识点回顾