一.进程问题的重点总结
1.进程
①进程 = PCB(进程控制块,一种数据结构,形成环境封闭,使得并发运行的程序具有可再现性) + 程序的代码和数据
②进程是程序能够正确的并发执行的一种方式
③进程是一个独立运行的实体和单位
④进程具有动态的含义,进程和程序是不同的概念
2.进程的特征
①结构特征(PCB + 数据段 + 进程段 + 系统栈 + 用户栈 = 进程映像)
②动态性(创建产生、调度执行、撤销消亡)
③并发性
④独立性
⑤异步性
3.进程的状态及转化
4.进程的同步
①主要协调进程在运行次序上的关系,常引入信号量机制来完成协调。
②信号量的使用特点是:对同一信号量S,wait(S)和signal(S)出现在不同的进程中。
注:wait & signal = P & V, 在伪代码中写哪一组都可以,但是要注意必须搭配成对出现
5.进程的互斥
①多个进程在使用一个临界资源时必须以互斥的方式使用,在这部分信号量使用的特点是:对于一个互斥信号量M,PV操作一般必须成对出现在同一进程之中。
②在同步和互斥中,一个或多个进程的wait操作(P操作)顺序不能颠倒,否则将发生死锁。
③一般先执行协调信号量的P操作,再执行互斥信号量的P操作,但多个V操作的排列顺序不影响进程的并发执行。
6.PV原语的详细解释
(1)PV原语简单介绍:
①定义:
PV原语通过操作信号量来处理进程间的同步与互斥的问题。其核心就是一段不可分割不可中断的程序。 信号量的概念1965年由著名的荷兰计算机科学家Dijkstra提出,其基本思路是用一种新的变量类型(semaphore)来记录当前可用资源的数量。
②semaphore
semaphore有两种实现方式:
semaphore的取值必须大于或等于0。0表示当前已没有空闲资源,而正数表示当前空闲资源的数量;semaphore的取值可正可负,负数的绝对值表示正在等待进入临界区的进程个数。
信号量定义操作(伪码)
structure semaphore{
int count;
queueType queue;
}
③P原语的操作(伪码)
void P(semaphore s){
s.count--;
if(s.count < 0){
-阻塞当前进程;
-把当前进程插入等待队列;
}
}
④V原语的操作(伪码)
void V(semaphore s){
s.count++;
if(s.count <= 0){
-把一个进程P从等待队列当中移除;
-把进程P插到就绪队列;
}
}
⑤利用信号量实现进程的互斥(类似于加锁)
/**/
const int n = /*进程数*/
semaphore s = 1; /*临界资源数量为1个*/
void process(int i){
while(true){
/*其他部分*/
P(s);
/*临界区*/
V(s);
/*其他部分*/
}
}
二.进程问题习题及解析(PV原语)
例1.某寺庙,有小和尚老和尚若干。有一水缸,由小和尚提水入缸供老和尚饮用。水缸可容10桶水,水取自同一井中。水井径窄,每次只能容下一个桶取水。水桶总数为3个。每人一次取缸水仅为1桶,且不可同时进行。试用记录信号量给出有关取水、入水的算法描述。
题目分析:
①3个水桶为临界资源需要互斥使用,定义并初始化信号量bucket=3。
②水井一次只能允许下一个水桶取水,定义并初始化互斥信号量well=1;
③水缸一次只能允许一个人取水,定义并初始化互斥信号量jar=1;
④empty和full用于老和尚与小和尚之间的制约关系,定义并初始化empty=10,开始时缸中没有水,因此full=0。
⑤这是一个生产者-消费者问题。小和尚取水条件需要确定:是否有桶、缸是否有人取水、缸是否为空、井是否能放桶。老和尚取水条件需要确定:是否有桶、缸是否有人取水、缸是否为空。
semaphore bucket = 3, jar = 1, well = 1, empty = 10, full = 0;
/*小和尚入水算法*/
young_monk(){
while(1){
/*查看缸中是否有空位*/
P(empty);
/*查看是否有可用的桶*/
P(bucket);
/*满足以上两个条件后,看是否井中有桶,如果没有,则从从井中取水*/
P(well);
从井中取水;
V(well);
/*取水后倒入水缸*/
P(jar);
倒入水缸;
V(jar);
/*增加一个桶的信号量*/
V(backet);
/*增加一个缸中水的数目的信号量*/
V(full);
}
}
/*老和尚取水算法*/
old_monk(){
while(1){
/*查看桶是否有水*/
P(full);
/*查看是否有空桶*/
P(backet);
/*从水缸中取水,验证是否只有一个人在取水*/
P(jar);
从缸中取水;
V(jar);
/*增加一个空桶的信号量*/
V(backet);
/*增加一个缸中空位的信号量*/
V(empty);
从桶中倒出饮用;
}
}
例2.设有3个进程A、B、C,其中A与B构成一对生产者与消费者(A为生产者,B为消费者),共享一个由n个缓冲区组成的缓冲池;B与C也构成一对生产者与消费者(此时B为生产者,C为消费者),共享另一个由m个缓冲区组成的缓冲池。用信号量机制协调它们的同步问题。
题目分析:
①A与B是一个生产者与消费者问题,缓冲池为n,因此定义并初始化一组full1=0,empty1=n,mutex1=1。
②B与C是一个生产者与消费者问题,缓冲池为m,因此定义并初始化一组full2=0,empty2=m,mutex2=1。
③其中,B既是生产者又是消费者,因此代码段中应该包含两个部分。
Semaphore full1 = 0, full2 = 0, empty1 = n, empty2= m, mutex1 = 1, mutex2 = 1;
int in1 = 0, out1 = 0, in2 = 0, out2 = 0;
Buffer buffer1[n], buffer2[m];
A( ){
while(true){
/*只有生产者部分*/
to produce an item;
P(empty1);
P(mutex1);
把产品放入buffer1[in1];
in1 = (in1+1) mod n;
V(mutex1);
V(full1);
}
}
//在没有特殊条件说明下,生产者部分和消费者部分可以颠倒,但是一般作为生产者在前,消费者在后。
B( ){
while(true){
/*生产者部分*/
P(full1);
P(mutex1);
从buffer1[out1]获得产品;
out1 = (out1+1) mod n;
V(mutex1);
V(empty1);
/*消费者部分*/
P(empty2);
P(mutex2);
把产品放入buffer2[in2];
in2 = (in2+1) mod m;
V(mutex2)
V(full2);
}
}
C( ){
while(true){
/*只有消费者部分*/
P(full2);
P(mutex2);
从buffer2[out2]获得产品;
out2 = (out2+1) mod m;
V(mutex2);
V(empty2);
}
}
例3.桌上有一只盘子,每次只能放入一个水果。爸爸专向盘子里放苹果,妈妈专向盘子里面放橘子,唯一的儿子专吃盘子中的橘子,唯一的女儿专吃盘子中的苹果。仅当盘子空闲时,爸爸或妈妈才可向盘子里存放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿才可从盘子里取出一个水果。把爸爸、妈妈、儿子、女儿看作4个进程,请用信号量机制进行管理,使得4个进程能正确地并发执行。
题目分析:
①父亲与女儿是一个1:1的生产者与消费者问题,具有专属关系。母亲与儿子是一个1:1的生产者与消费者问题,具有专属关系。
②其中,盘子作为临界资源,其容量为1,这一点非常重要,因为采用一般的生-消问题来解决此问题会有代码冗余,因此初始化变量plate=1,将原有的empty与mutex合并,减少代码数量,更加简洁。
semaphore plate = 1, apple = 0, orange = 0
dad(){
while(true){
to prepare an apple;
P(plate);
to put the apple on the plate;
V(apple);
}
}
mom(){
while(true){
to prepare an orange;
P(plate);
to put the orange on the plate;
V(orange);
}
}
son(){
while(true){
P(orange);
to get an orange from the plate;
V(plate);
to eat the orange;
}
}
daughter(){
while(true){
P(apple);
to get the apple from the plate;
V(plate);
to eat the apple
}
}
注:如果此问题中盘子的容量大于1,则它将变为普通的生产者和消费者问题,加入empty=n,并将plate改为mutex,再修改相应代码即可。
例4.读者写者优先问题:如果有多个进程要求读文件,这时又有进程要求写文件,则在当前所有的读完成后,应先完成写操作,然后才能读,且在写的过程中,如果有另一进程要求写操作,则在当前写结束后,应接着完成写操作,直到没有写进程时,才允许读。
题目分析:
①如果没有写进程,应该让读进程随便进出。
②一旦有写进程到来,则需要一个信号量,使得此后到来的读进程都阻塞等待。
③需要一个信号量,保证当前有读进程时,不允许写。
④如果有写进程到来,则保证当前读进程读完成后,才可以写。
semaphore readcount = 0, writecount = 0, readok = 1, rmutex = 1, wmutex = 1, mutex = 1;
/*
变量:
readcount = 0,用于记录读者的数量。
writecount = 0,用于记录写者的数量。
信号量:
readok = 1,用于协调读和写,保证写到来时,新来的读阻塞。
rmutex = 1,和读优先中的互斥变量作用相同。
wmutex = 1,用于控制互斥访问writecount变量。
mutex = 1, 用于控制互斥写操作。
*/
/*读者问题*/
reader(){
while(true){
P(readok);
P(rmutex);
if readcount == 0
P(mutext);
readcount ++;
V(rmutex);
V(readok);
to read;
P(rmutex);
readcount --;
if readcount == 0
V(mutext);
V(rmutex);
}
}
/*写者问题*/
writer(){
while(true){
P(wmutex)
if writecount == 0
P(readok);
writecount ++;
V(wmutex);
P(mutex);
to write;
V(mutex);
P(wmutex);
writecount --;
if writecount == 0
V(readok);
V(wmutex);
}
}