一、 管程的概念
1. 管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
2. 进程只能互斥得使用管程,即当一个进程使用管程时,另一个进程必须等待。当一个进程使用完管程后,它必须释放管程并唤醒等待管程的某一个进程。
3. 在管程入口处的等待队列称为入口等待队列,由于进程会执行唤醒操作,因此可能有多个等待使用管程的队列,这样的队列称为紧急队列,它的优先级高于等待队列。
二、 管程的特征
1. 模块化。管程是一个基本的软件模块,可以被单独编译。
2. 抽象数据类型。管程中封装了数据及对于数据的操作,这点有点像面向对象编程语言中的类。
3. 信息隐藏。管程外的进程或其他软件模块只能通过管程对外的接口来访问管程提供的操作,管程内部的实现细节对外界是透明的。
4. 使用的互斥性。任何一个时刻,管程只能由一个进程使用。进入管程时的互斥由编译器负责完成。
三、 enter过程、leave过程、条件型变量c、wait(c) 、signal(c)
1. enter过程:一个进程进入管程前要提出申请,一般由管程提供一个外部过程--enter过程。如Monitor.enter()表示进程调用管程Monitor外部过程enter进入管程。
2. leave过程:当一个进程离开管程时,如果紧急队列不空,那么它就必须负责唤醒紧急队列中的一个进程,此时也由管程提供一个外部过程—leave过程,如Monitor.leave()表示进程调用管程Monitor外部过程leave离开管程。
3. 条件型变量c:条件型变量c实际上是一个指针,它指向一个等待该条件的PCB队列。如notfull表示缓冲区不满,如果缓冲区已满,那么将要在缓冲区写入数据的进程就要等待notfull,即wait(notfull)。相应的,如果一个进程在缓冲区读数据,当它读完一个数据后,要执行signal(notempty),表示已经释放了一个缓冲区单元。
4. wait(c):wait(c)表示为进入管程的进程分配某种类型的资源,如果此时这种资源可用,那么进程使用,否则进程被阻塞,进入紧急队列。
5. signal(c):signal(c)表示进入管程的进程使用的某种资源要释放,此时进程会唤醒由于等待这种资源而进入紧急队列中的第一个进程。
四、 应用实例
案例一:生产者消费者问题。生产者进程将产品放入某一缓冲区,消费者进程到此缓冲区中取产品。这个过程必须保证:1. 当缓冲区有剩余空间时,生产者才能在其中放入产品;2. 当缓冲区有数据时,消费者才能在其中取出产品。
解决方案:使用管程机制来实现生产者和消费者之间的同步互斥问题
1. 假设有一基本管程monitor,提供了enter、leave、signal、wait等操作;
2. 条件变量notfull表示缓冲区不满,条件变量notempty表示缓冲区不空;
3. 缓冲区buff[0...n-1]用来存放产品,最大可放n件产品;
4. 定义整型变量count表示缓冲区当前的产品数,指针in指向缓冲区当前第一个空的位置,指针out指向缓冲区当前第一个不空的位置;
5. 定义过程add(ItemType item)
add(ItemTypeitem) //生产者进程在缓冲区放入产品
{ if(count==n) wait(notfull); //如果此时缓冲区已满,那么进程必须等待notfull,
这意味着进程已经被阻塞到紧急队列里
buff[in]=item; //否则在第一个空的位置放入产品
in=(in+1)%n; //指针循环加1
count++;
signal(notempty); //此时缓冲区已经多了一个产品,也就是说生产者进
程去唤醒因取不到产品被阻塞的消费者进程
}
6. 定义过程ItemType remove()
ItemTyperemove() //消费者进程在缓冲区取出产品
{
if(count==0) wait(notempty); //如果缓冲区没有产品,那么消费者必须等待notempty,
也就是被阻塞到紧急队列中去
item=buff[out]; //消费者从第一个不空的位置取出产品
out=(out+1)%n;
signal(notfull); //此时缓冲区多了一个空的单元,也就是消费者进程
去唤醒因缓冲区已满而不能放入产品的生产者进程
return item;
}
7. 生产者进程代码段
while(true)
{ produce(&item); //生成出一件产品
monitor. enter(); //进入管程
monitor. add(); //调用add方法,放入产品
monitor. leave(); //离开管程
}
8. 消费者进程代码段
while(true)
{ monitor.enter();
item=monitor. remove(); //取出产品
monitor. leave();
consumer(&item); //进行消费
}
案例二:读者—写者问题。现有一个缓冲区,有若干读者进程和若干写者进程。读者进程在缓冲区读数据,写者进程在缓冲区写入数据。这个过程必须保证:1. 读者进程之间不需要互斥;2.写者进程之间必须互斥,即当一个写者进程在缓冲区写入数据时,别的写者进程必须被阻塞;3. 读者进程和写者进程必须互斥,即当有读者进程在读数据,写者进程必须被阻塞,有写者进程在写数据时,读者进程必须被阻塞。
解决方案:采用管程机制来解决读者—写者问题
1. 假设已经有一个基本管程Monitor提供了enter、leave、signal、wait等操作;
2. 定义条件变量r表示可以对缓冲区读,条件变量w表示可以对缓冲区写;
3. 定义布尔类型变量IsWriting表示当前有写者进程在缓冲区写数据;
4. 整型变量read_count表示读数据的个数;
5. 定义过程startRead()
void startRead()
{ if(IsWriting) wait(r); //此时缓冲区有写者进程在写数据,那么读者进程等待r,
也就是读者进程被阻塞到紧急队列中
read_count++; //否则,读出数据
signal(r); //唤醒被阻塞的读者进程
}
6. 定义过程endRead()
void endRead()
{ read_count--;
if(read_count= =0) signal(w); // 此时表示所有读者进程都已经读完数据,
那么唤醒被阻塞的写 者进程
}
7. 定义过程startWrite
void startWrite()
{ if(read_count!=0 || IsWriting) wait(w); //此时表示如果有读者进程存在或者其他写者
进程存在,那么将要写数据的写者进程被阻塞
IsWriting=true;
}
8. 定义过程endWrite()
void endWrite()
{ IsWriting=false;
if(r!=null) signal(r); //如果有读者进程存在,那么唤醒读者进程
else signal(w); //否则唤醒写者进程
}
9. 读者进程代码段
while(true)
{ Monitor. enter();
Monitor. startRead();
read();
Monitor. endRead();
Monitor. leave();
}
10. 写者进程代码段
while(true)
{ Monitor. enter();
Monitor. startWrite();
write();
Monitor. endWrite();
Monitor. leave();
}
注:需要互斥的操作放在管程之间
案例三:哲学家进餐问题。
用三种不同状态表示哲学家的活动:进餐、饥饿、思考。 (thinking,hungry,eating)state[5];
为第i个(i=0,..,4)哲学家设置一个条件变量self[i],当哲学家饥饿又不能获得筷子时,用self[i].wait来阻塞自己。condition self[5];
管程设置三个函数:pickup取筷子,putdown放筷子,test测试是否具备进餐条件。
Monitor DP;
(thinking, hungry, eating) state[5]
condition self[5];
void Entry pickup(int i) /*第i号哲学家取筷子*/
{ state[i] = hungry;
test(i);
if(state[i]!=eating)
self[i].wait;
}
void Entry putdown(int i) /*第i号哲学家放下筷子*/
{ state[i] = thinking;
test((i+4)%5);
test((i+1)%5);
}
void test(int i) /*测试第i号哲学家是否具备进餐条件*/
{ if (state[(i+4) % 5]<>eating) && (state[i]=hungry)&& (state[(i+1) % 5]<>eating)
{ state[i] = eating;
self[i].signal;
}
}
for(i = 0; i < 5; i++)
state[i] = thinking;
cobegin
void philosopher(int i)
{ while(true)
{ thinking;
DP.pickup(i);
eating;
DP.putdown(i);
}
}
coend
解法2:
monitor PC /*建立管程*/
{ semaphore chopstick [5] = {1,1,1,1,1};
X: condition; /*定义条件变量*/
void Get (int I) /*定义取筷子过程*/
{ If chopstick[I]=1 and chopstick [ ( i+1 )%5] =1 then
get the chopstick [I] and chopstick[ ( i+1 ) % 5];
else
X.wait;/*左右筷子没有,则等待*/
}
void Put (int i) /*定义放下筷子过程*/
{ put the chopstick [I ] and chopstick[( i+1 ) % 5];
If X.quene then
X.signal; /*唤醒等待筷子 的哲学家*/
}
}
第I 个哲学家的活动:
void philosopher(int I)
{
while(true)
{ 思考;
get (I);
进餐;
put (I);
}
}