-
do {
-
entry section;
//进入区
-
critical section;
//临界区
-
exit section;
//退出区
-
remainder section;
//剩余区
-
}
while (
true)
-
"code" class=
"cpp">
// P0进程
-
while(turn!=
0);
-
critical section;
-
turn=
1;
-
remainder section;
-
// P1进程
-
while(turn!=
1);
// 进入区
-
critical section;
// 临界区
-
turn =
0;
// 退出区
-
remainder section;
// 剩余区
-
// Pi 进程
-
while(flag[j]);
// ①
-
flag[i]=TRUE;
// ③
-
critical section;
-
flag[i] = FALSE;
-
remainder section;
-
// Pj 进程
-
while(flag[i]);
// ② 进入区
-
flag[j] =TRUE;
// ④ 进入区
-
critical section;
// 临界区
-
flag[j] = FALSE;
// 退出区
-
remainder section;
// 剩余区
-
// Pi进程
-
flag[i] =TRUE;
-
while(flag[j]);
-
critical section;
-
flag[i] =FLASE;
-
remainder section;
-
// Pj进程
-
flag[j] =TRUE;
// 进入区
-
while(flag[i]);
// 进入区
-
critical section;
// 临界区
-
flag [j] =FLASE;
// 退出区
-
remainder section;
// 剩余区
-
// Pi进程
-
flag[i]=TURE; turn=j;
-
while(flag[j]&&turn==j);
-
critical section;
-
flag[i]=FLASE;
-
remainder section;
-
// Pj进程
-
flag[j] =TRUE;turn=i;
// 进入区
-
while(flag[i]&&turn==i);
// 进入区
-
critical section;
// 临界区
-
flag[j]=FLASE;
// 退出区
-
remainder section;
// 剩余区
-
boolean TestAndSet(boolean *lock){
-
boolean old;
-
old = *
lock;
-
*
lock=
true;
-
return old;
-
}
可以为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正被占用,初值为false。在进程访问临界资源之前,利用TestAndSet检查和修改标志lock;若有进程在临界区,则重复检查,直到进程退出。利用该指令实现进程互斥的算法描述如下:
-
while TestAndSet (& 1 ock);
-
// 进程的临界区代码段;
-
lock=
false;
-
// 进程的其他代码
Swap指令:该指令的功能是交换两个字节的内容。其功能描述如下。
注意:以上对TestAndSet和Swap指令的描述仅仅是功能实现,并非软件实现定义,事实上它们是由硬件逻辑直接实现的,不会被中断。应为每个临界资源设置了一个共享布尔变量lock,初值为false;在每个进程中再设置一个局部布尔变量key,用于与lock交换信息。在进入临界区之前先利用Swap指令交换lock 与key的内容,然后检查key的状态;有进程在临界区时,重复交换和检查过程,直到进程退出。利用Swap指令实现进程互斥的算法描述如下:
Swap(boolean *a, boolean *b){ boolean temp; Temp=*a; *a = *b; *b = temp; }
-
key=
true;
-
while(key!=
false)
-
Swap(&
lock, &key);
-
// 进程的临界区代码段;
-
lock=
false;
-
// 进程的其他代码;
硬件方法的优点:适用于任意数目的进程,不管是单处理机还是多处理机;简单、容易验证其正确性。可以支持进程内有多个临界区,只需为每个临界区设立一个布尔变量。硬件方法的缺点:进程等待进入临界区时要耗费处理机时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致“饥饿”现象。
-
wait(S){
-
while (S<=
0);
-
S=S
-1;
-
}
-
signal(S){
-
S=S+
1;
-
}
wait操作中,只要信号量S<=0,就会不断地测试。因此,该机制并未遵循“让权等待” 的准则,而是使进程处于“忙等”的状态。
-
typedef
struct{
-
int
value;
-
struct process *L;
-
} semaphore;
相应的wait(S)和signal(S)的操作如下:
-
void wait (semaphore S) {
//相当于申请资源
-
S.value--;
-
if(S.value<
0) {
-
add
this process to S.L;
-
block(S.L);
-
}
-
}
wait操作,S.value--,表示进程请求一个该类资源,当S.value<0时,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞,放弃处理机,并插入到该类资源的等待队列S.L中,可见该机制遵循了“让权等待”的准则。
void signal (semaphore S) { //相当于释放资源
S.value++;
if(S.value<=0){
remove a process P from S.L;
wakeup(P);
}
}
signal操作,表示进程释放一个资源,使系统中可供分配的该类资源数增1,故S.value++。若加1后仍是S.value<=0,则表示在S.L中仍有等待该资源的进程被阻塞,故还应调用wakeup 原语,将S.L中的第一个等待进程唤醒。
semaphore S = 0; //初始化信号量
P1 ( ) {
// …
x; //语句x
V(S); //告诉进程P2,语句乂已经完成
}
P2()){
// …
P(S) ; //检查语句x是否运行完成
y; // 检查无误,运行y语句
// …
}
semaphore S = 1; //初化信号量
P1 ( ) {
// …
P(S); // 准备开始访问临界资源,加锁
// 进程P1的临界区
V(S); // 访问结束,解锁
// …
}
P2() {
// …
P(S); //准备开始访问临界资源,加锁
// 进程P2的临界区;
V(S); // 访问结束,解锁
// …
}
互斥的实现是不同进程对同一信号量进行P、V操作,一个进程在成功地对信号量执行了P操作后进入临界区,并在退出临界区后,由该进程本身对该信号量执行V操作,表示当前没有进程进入临界区,可以让其他进程进入。
-
semaphore al=a2=bl=b2=c=d=e=
0;
//初始化信号量
-
S1() {
-
// …
-
V(al); V(a2) ;
//S1已经运行完成
-
}
-
S2() {
-
P(a1);
//检查S1是否运行完成
-
// …
-
V(bl); V(b2);
// S2已经运行完成
-
}
-
S3() {
-
P(a2);
//检查S1是否已经运行完成
-
// …
-
V(c);
//S3已经运行完成
-
}
-
S4() {
-
P(b1);
//检查S2是否已经运行完成
-
// …
-
V(d);
//S4已经运行完成
-
}
-
S5() {
-
P(b2);
//检查S2是否已经运行完成
-
// …
-
V(e);
// S5已经运行完成
-
}
-
S6() {
-
P(c);
//检查S3是否已经运行完成
-
P(d);
//检查S4是否已经运行完成
-
P(e);
//检查S5是否已经运行完成
-
// …;
-
}
-
semaphore mutex=
1;
//临界区互斥信号量
-
semaphore empty=n;
//空闲缓冲区
-
semaphore full=
0;
//缓冲区初始化为空
-
producer () {
//生产者进程
-
while(
1){
-
produce an item in nextp;
//生产数据
-
P(empty);
//获取空缓冲区单元
-
P(mutex);
//进入临界区.
-
add nextp to buffer;
//将数据放入缓冲区
-
V(mutex);
//离开临界区,释放互斥信号量
-
V(full);
//满缓冲区数加1
-
}
-
}
-
-
consumer () {
//消费者进程
-
while(
1){
-
P(full);
//获取满缓冲区单元
-
P(mutex);
// 进入临界区
-
remove an item from buffer;
//从缓冲区中取出数据
-
V (mutex);
//离开临界区,释放互斥信号量
-
V (empty) ;
//空缓冲区数加1
-
consume the item;
//消费数据
-
}
-
}
该类问题要注意对缓冲区大小为n的处理,当缓冲区中有空时便可对empty变量执行P 操作,一旦取走一个产品便要执行V操作以释放空闲区。对empty和full变量的P操作必须放在对mutex的P操作之前。如果生产者进程先执行P(mutex),然后执行P(empty),消费者执行P(mutex),然后执行P(fall),这样可不可以?答案是否定的。设想生产者进程已经将缓冲区放满,消费者进程并没有取产品,即empty = 0,当下次仍然是生产者进程运行时,它先执行P(mutex)封锁信号量,再执行P(empty)时将被阻塞,希望消费者取出产品后将其唤醒。轮到消费者进程运行时,它先执行P(mutex),然而由于生产者进程已经封锁mutex信号量,消费者进程也会被阻塞,这样一来生产者、消费者进程都将阻塞,都指望对方唤醒自己,陷入了无休止的等待。同理,如果消费者进程已经将缓冲区取空,即 full = 0,下次如果还是消费者先运行,也会出现类似的死锁。不过生产者释放信号量时,mutex、full先释放哪一个无所谓,消费者先释放mutex还是empty都可以。
-
semaphore plate=l, apple=
0, orange=
0;
-
dad() {
//父亲进程
-
while (
1) {
-
prepare an apple;
-
P(plate) ;
//互斥向盘中取、放水果
-
put the apple on the plate;
//向盘中放苹果
-
V(apple);
//允许取苹果
-
}
-
}
-
-
mom() {
// 母亲进程
-
while(
1) {
-
prepare an orange;
-
P(plate);
//互斥向盘中取、放水果
-
put the orange on the plate;
//向盘中放橘子
-
V(orange);
//允许取橘子
-
}
-
}
-
-
son(){
//儿子进程
-
while(
1){
-
P(orange) ;
//互斥向盘中取橘子
-
take an orange from the plate;
-
V(plate);
//允许向盘中取、放水果
-
eat the orange;
-
}
-
}
-
-
daughter () {
//女儿进程
-
while(
1) {
-
P(apple);
// 互斥向盘中取苹果
-
take an apple from the plate;
-
V(plate);
//运行向盘中取、放水果
-
eat the apple;
-
}
-
}
进程间的关系如图2-9所示。dad()和daughter()、mam()和son()必须连续执行,正因为如此,也只能在女儿拿走苹果后,或儿子拿走橘子后才能释放盘子,即V(plate)操作。
-
int count=
0;
//用于记录当前的读者数量
-
semaphore mutex=
1;
//用于保护更新count变量时的互斥
-
semaphore rw=
1;
//用于保证读者和写者互斥地访问文件
-
writer () {
//写者进程
-
while (
1){
-
P(rw);
// 互斥访问共享文件
-
Writing;
//写入
-
V(rw) ;
//释放共享文件
-
}
-
}
-
-
reader () {
// 读者进程
-
while(
1){
-
P (mutex) ;
//互斥访问count变量
-
if (count==
0)
//当第一个读进程读共享文件时
-
P(rw);
//阻止写进程写
-
count++;
//读者计数器加1
-
V (mutex) ;
//释放互斥变量count
-
reading;
//读取
-
P (mutex) ;
//互斥访问count变量
-
count--;
//读者计数器减1
-
if (count==
0)
//当最后一个读进程读完共享文件
-
V(rw) ;
//允许写进程写
-
V (mutex) ;
//释放互斥变量 count
-
}
-
}
-
int count =
0;
//用于记录当前的读者数量
-
semaphore mutex =
1;
//用于保护更新count变量时的互斥
-
semaphore rw=
1;
//用于保证读者和写者互斥地访问文件
-
semaphore w=
1;
//用于实现“写优先”
-
-
writer(){
-
while(
1){
-
P(w);
//在无写进程请求时进入
-
P(rw);
//互斥访问共享文件
-
writing;
//写入
-
V(rw);
// 释放共享文件
-
V(w) ;
//恢复对共享支件的访问
-
}
-
}
-
-
reader () {
//读者进程
-
while (
1){
-
P (w) ;
// 在无写进程请求时进入
-
P (mutex);
// 互斥访问count变量
-
-
if (count==
0)
//当第一个读进程读共享文件时
-
P(rw);
//阻止写进程写
-
-
count++;
//读者计数器加1
-
V (mutex) ;
//释放互斥变量count
-
V(w);
//恢复对共享文件的访问
-
reading;
//读取
-
P (mutex) ;
//互斥访问count变量
-
count--;
//读者计数器减1
-
-
if (count==
0)
//当最后一个读进程读完共享文件
-
V(rw);
//允许写进程写
-
-
V (mutex);
//释放互斥变量count
-
}
-
}
-
semaphore chopstick[
5] = {
1,
1,
1,
1,
1};
//定义信号量数组chopstick[5],并初始化
-
Pi(){
//i号哲学家的进程
-
do{
-
P (chopstick[i] ) ;
//取左边筷子
-
P (chopstick[(i+
1) %
5] ) ;
//取右边篌子
-
eat;
//进餐
-
V(chopstick[i]) ;
//放回左边筷子
-
V(chopstick[(i+l)%
5]);
//放回右边筷子
-
think;
//思考
-
}
while (
1);
-
}
该算法存在以下问题:当五个哲学家都想要进餐,分别拿起他们左边筷子的时候(都恰好执行完wait(chopstick[i]);)筷子已经被拿光了,等到他们再想拿右边的筷子的时候(执行 wait(chopstick[(i+l)%5]);)就全被阻塞了,这就出现了死锁。
-
semaphore chopstick[
5] = {
1,
1,
1,
1,
1};
//初始化信号量
-
semaphore mutex=l;
//设置取筷子的信号量
-
Pi(){
//i号哲学家的进程
-
do{
-
P (mutex) ;
//在取筷子前获得互斥量
-
P (chopstick [i]) ;
//取左边筷子
-
P (chopstick[ (i+
1) %
5]) ;
//取右边筷子
-
V (mutex) ;
//释放取筷子的信号量
-
eat;
//进餐
-
V(chopstick[i] ) ;
//放回左边筷子
-
V(chopstick[ (i+l)%
5]) ;
//放回右边筷子
-
think;
// 思考
-
}
while(
1);
-
}
此外还可以釆用AND型信号量机制来解决哲学家进餐问题,有兴趣的读者可以查阅相关资料,自行思考。
-
int random;
//存储随机数
-
semaphore offer1=
0;
//定义信号量对应烟草和纸组合的资源
-
semaphore offer2=
0;
//定义信号量对应烟草和胶水组合的资源
-
semaphore offer3=
0;
//定义信号量对应纸和胶水组合的资源
-
semaphore finish=
0;
//定义信号量表示抽烟是否完成
-
-
//供应者
-
while(
1){
-
random = 任意一个整数随机数;
-
random=random%
3;
-
if(random==
0)
-
V(offerl) ;
//提供烟草和纸
-
else
if(random==l)
-
V(offer2);
//提供烟草和胶水
-
else
-
V(offer3)
//提供纸和胶水
-
// 任意两种材料放在桌子上;
-
P(finish);
-
}
-
-
//拥有烟草者
-
while(
1){
-
P (offer3);
-
// 拿纸和胶水,卷成烟,抽掉;
-
V(finish);
-
}
-
-
//拥有纸者
-
while(
1){
-
P(offer2);
-
// 烟草和胶水,卷成烟,抽掉;
-
V(finish);
-
}
-
-
//拥有胶水者
-
while(
1){
-
P(offer1);
-
// 拿烟草和纸,卷成烟,抽掉;
-
v(finish);
-
}