进程同步、互斥
引入
进程是并发、异步执行的,多个进程运行就可能导致冲突混乱。比如:系统中只有一台打印机,多个进程都需要去访问,如果没有协调 就会导致多个结果交织在一起,无法辨认。
进程同步就是要协调多个相关进程的执行次序,并发执行时能够有效合作、共享资源。
两个概念
临界资源:多进程应该采取互斥方式访问的共享资源,即临界资源。某个时刻只能有一个进程使用。
临界区:每个进程中访问临界资源的那段代码称为临界区。
若能保证诸进程互斥地进入自己的临界区,即可实现诸进程对临界资源的互斥访问。
因此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看它是否正被访问。进入区时设置标志(正在访问),退出区时恢复标志(无访问,空闲中)。
两种相互制约
进程同步
进程间的一种直接制约关系---源于进程合作。为完成某个任务而建立的多个进程,他们直接需要协调执行次序、传递信息等形成的制约关系。
如,进程A通过单缓冲向进程B提供数据。当缓冲区为空时,进程B阻塞,直到进程A向缓冲区放入数据 进程B被唤醒。
进程互斥
进程间的一种间接制约关系---源于资源共享。当一个进程使用临界资源时,另一个进程必须等待。当使用临界资源的进程退出后,这个进程才会解除阻塞状态。进程需要互斥的进入自己的临界区。
如,打印机一次只能一个进程访问,当进程A访问了打印机,进程B只能等待。
同步机制都应遵循下述四条准则:
空闲让进:当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效地利用临界资源。
忙则等待:当已有进程进入临界区时,表明临界资源正在被访问,因而其它试图进 入临界区的进程必须等待,以保证对临界资源的互斥访问。
有限等待:对要求访问临界资源的进程,应保证在有限时间内能进入自己的临界区, 以免陷入“死等”状态。
让权等待:当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙 等”状态
两种进程同步工具
注:为避免该篇过于冗长,这两种机制单独总结了。后面的经典同步问题举例,下面两个是基础。
信号量(Semaphores)机制:https://www.cnblogs.com/fanglongxiang/p/12907876.html
管程(Monitors)机制:https://www.cnblogs.com/fanglongxiang/p/12908685.html
经典进程同步问题举例
生产者-消费者问题
问题描述
有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。
在两者之间设置了一个具有n 个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品去消费。不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
解决
1.记录型变量方法
具体看下面描述中的注释,应该很清晰了。所有信号量的操作,wait()和signal()都需成对出现。
简单理解下面empty,full信号量,empty是生产者需要的(相当于空位数),生产者会使empty减少,为0时生产者不能往缓冲区放产品 进程挂起,消费者消费会使empty增加。同理,full是消费者需要的(相当于产品数),消费者使full减少,为0时消费者进程挂起,生产者会使full增加。
注意:生产者描述中或消费者描述中,多个wait()操作顺序不能变,否则可能引起死锁。
///信号量及变量定义 //3个信号量 //mutex:互斥信号量 初始值1; //empty:资源信号量,表示空缓冲 初始值为n; //full:资源信号量,表示缓冲区满 初始值为0 Var mutex,empty,full: semaphore:=1,n,0; //缓冲区,一个大小为n的数组 buffer:array[0,…,n-1] //输入,输出,初始值为0 in,out: integer:=0,0; ///过程描述 begin //生产者描述 Producer: begin repeat //生产了一个产品,nextp:next product producer an item nextp; //申请empty。若empty为0时,阻塞挂起,进入等待队列 wait(empty); //申请mutex,mutex为互斥,避免同一时刻对缓冲区进行操作引起混乱 wait(mutex); //产品进入缓冲,in位置进1 buffer(in):=nextp; in:=(in+1) mod n; //释放互斥信号量 signal(mutex); //释放full,full加1 signal(full); until false; end //消费者描述 Consumer: begin repeat //申请full.若full为0,则挂起等待 wait(full); //申请互斥mutex,避免同一时刻对缓冲区进行操作引起混乱 wait(mutex); //取出产品,out位置进1 nextc:=buffer(out); out:=(out+1) mod n; //释放互斥信号量 signal(mutex); //释放empty,empty加1 signal(empty); //消费一个产品 consumer the item in nextc; until false; end end
2.管程方法
定义一个管程PC, 管程可描述如下,详情查看注释:
///管程中共享变量 //定义一个管程,producer-consumer,简称PC type producer-consumer=monitor //定义3个变量,in,out,count Var in,out,count: integer; //定义buffer数组,缓冲区 buffer: array[0, …, n-1] of item; //定义条件变量,notfull、notempty notfull,notempty:condition; ///管程中的一组操作 //生产者过程 procedure entry put(item) begin //如果count>=n, 生产者进程由于notfull不满足而进入相应等待队列挂起 if count>=n then notfull.wait; //如果count<n,则缓冲区仍有位置,放入产品 buffer(in):=nextp; in:=(in+1) mod n; count:=count+1; //放入后,notempty队列存在进程,则唤醒 if notempty.queue then notempty.signal; end ///消费者进程 consumer entry get(item) begin //count<=0时,缓冲区为空,则消费者进程由于不满足notempty而在相应队列中挂起 if count<=0 then notempty.wait; //count>0时,取出产品 nextc:=buffer(out); out:=(out+1) mod n; count:=count-1; //count-1后,若notfull条件队列不为空,则唤醒notfull中进程 if notfull.quene then notfull.signal; end ///管程初始化代码 begin in:=out:=0; count:=0 end
生产者和消费者问题可描述为:
///生产者 producer: begin repeat produce an item in nextp; PC.put(item); until false; end ///消费者 consumer: begin repeat PC.get(item); consume the item in nextc; until false; end
哲学家进餐问题
问题描述
有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。
进餐完毕,放下筷子继续思考。
解决
AND型变量方法
在哲学家进餐问题中,要求每个哲学家先获得两个临界资源(筷子)后方能进餐,这在本 质上就是信号量机制中所介绍的 AND型信号量 同步问题
如下:
Var chopstick array of semaphore:=(1,1,1,1,1); repeat ///思考 think; wait(chopstick[(i+1)mod 5],chopstick[i]); ///吃饭 eat; signal(chopstick[(i+1)mod 5],chopstick[i]); until false; ///不过多解释,具体请查看,信号量机制中的AND型信号量。