临界资源:
在某段时间内只允许一个进程使用的资源。进程之间互斥访问。实现对临界资源的共享。
eg:(硬件资源)打印机、磁带机,(软件资源)变量、文件。
临界:
每一个进程访问临界资源的那段代码。进程互斥的进入自己的临界区,就可保证互斥访问。
同步机制的四条准则:
- 空闲让进:(空闲时进程可以进)
- 忙则等待:(忙碌的时候进程等待)
- 有限等待:(对要求访问临界资源的进程,应保证在有限时间内能进入自己的临界区,以免陷入“死等”)
- 让权等待:(进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”)
同步机制
-
信号量机制
-
整形信号量——表示资源数量的整型量s。通过申请资源wait(s),释放资源signal(s)来实现互斥访问。wait、signal也称P、V操作
但是整形信号量无法实现让权等待。一个进程因为资源不够进入阻塞,其它进程也无法进入
-
记录型信号量——在整形信号量的基础上加入一个阻塞队列。若资源不够则进入阻塞队列并释放资源,这样就实现了让权等待
typedef struct{ int value;//资源数目 struct process_control_block *list;//阻塞链表 }semaphore; wait(semaphore s){ s.value--; if(s.value<0){//资源不够就放入阻塞列表 block(s.list); } } /*当s.value<0之后,value的绝对值是阻塞队列中的进程个数*/ signal(semaphore s){ s.value++;//阻塞队列中进程减少 if(s.value<=0){//阻塞队列中还有进程 wakeup(s.list);//这就让一些进程继续执行 } }
-
AND型信号量——当不止缺少一个资源时,缺少哪一个资源就把他放入哪一个资源的阻塞队列中
Swait(semaphore s1,s2,...,sn){ while(TRUE){ if(s1.value>=1 &&...&& sn.value>=1){ for(i=1;i<=n;i++){ si.value--; } break; } else{ /*找到第一个si<1时,重置程序计数器,把这个进程放在si的阻塞队列中*/ } } } signal(semaphore s){ while(TRUE){ for(i=1;i<=n;i++){ si.value++; /*把进程放入就绪队列*/ } } }
-
信号量集——当进程对某一资源的需求不止一个时,用tn表示进程还需资源数,dn表示进程共需资源数
semaphore s1,s2,...,sn; Swait(s1,t1,d1,...,sn,tn,dn){ while(TRUE){ if(s1.value>=t1 &&...&& sn.value>=tn){//条件函数不一样 for(i=1;i<=n;i++){ si.value-=di; } break; } else{ /*找到第一个si<t1时,重置程序计数器,把这个进程放在si的阻塞队列中*/ } } } signal(semaphore s1,s2,..,sn){ while(TRUE){ for(i=1;i<=n;i++){ si.value+=di; /*把进程放入就绪队列*/ } } }
-
-
管程机制
管程:代表共享资源的数据结构+实施操作的一组过程。
引入管程的原因:信号量机制是一种方便有效的进程同步工具。但每次访问临界资源时都需要进程自身进行wait(S)和signal(S)操作。这样,大量的同步操作分散在不同进程中,会给系统管理带来负担,另一方面同步操作使用不当容易导致死锁。
管程封装了同步操作,对进程隐蔽了同步细节,简化了同步功能的调用,避免了有意或无意的违法同步操作,给编程带来便利。
管程的特点:互斥性(一个管程中只能有一个活跃的进程)、共享性(管程可以互斥地被多个进程访问)、安全性、封装性
当进程缺少哪一个条件,就将该进程放入哪一个条件的阻塞队列中,同时释放管程。
-
硬件同步机制
进程同步与互斥问题
-
生产者与消费者
在生产者与消费者直接设置有n个缓冲区的缓冲池,——相当于一个循环队列。生产的数据放入缓冲池,消费者从缓冲池中拿数据。
信号量的设计:这里就用了互斥信号量和整型信号量。互斥信号量实现生产者与消费者的互斥访问。整型信号量为了保证资源足够才争取锁。/*全局变量*/ typedef struct{//循环缓冲表 ... } item; item buffer[n]; int in=out=0;//in指针,out指针 in=(in+1)%n out=(out+1)%n item nextp,nextc;//每次要生产、消费的产品 semphore mutex=1;//互斥信号量,初始为1 semphore empty=n,full=0 //资源信号量,整型信号量 /*主程序*/ void main(){ cobegin producer1;...producerm; consumer1;...consumern; coend } /*生产者*/ produceri: { repeat produce an item in nextp; wait(empty); //空缓冲区减少,wait顺序不能互换,确保有资源才争取锁 wait(mutex); //Swait(empty,mutex); //使用and信号量。多个资源一同申请,防止死锁。不用考虑先后顺序 buffer[in]=nextp; in=(in+1)%n; signal(mutex); signal(full); //生产过后要去通知消费者,满缓冲区增加 //Ssignal(mutex,full); //and信号量释放 until false; } /*消费者*/ consumerj: { repeat wait(full); //满缓冲区减少 wait(mutex); //Swait(full,mutex); //使用and信号量。 nextc=buffer[out]; out=(out+1)%n; signal(mutex); signal(empty); //通知生产者空缓冲区增加 //Ssignal(mutex,empty); //and信号量释放 consumer the item in nextc; until false; }
-
读者与写者
读进程可以同时读,但是写进程之间、写进程和读进程要保持互斥。
信号量的设计:- 为了实现写互斥,则需要写一个写互斥信号量
wmutex = 1
- 读进程之间不互斥,则用一个计数器记录读进程个数
readcount
,readcount
为临界资源,需要对其设置互斥信号量rmutex = 1
- 读写之前都需要申请
wmutex
,当readcount=0
时读进程才需要申请wmutex
,避免第二次申请失败
int readcount=0; semaphore wmutex=1,rmutex=1; main(){ cobegin read1,read2,..... write1,write2.... coend } write{ repeat wait(wmutex); perform write operation; signal(wmutex); until false; } read{ repeat wait(rmutex); // 访问readcount之前需要申请互斥信号量 if(readcount==0) then wait(wmutex);//如果没有读进程,就申请写信号量,避免写进程执行。加一个条件避免重复申请。 readcount++; signal(rmutex);//可以读了,就释放,便于其它读进程读。 perform read operation; wait(rmutex); readcount--; if(readcount==0) then signal(wmutex);//通知写进程,可以写了 signal(rmutex); until false; }
- 为了实现写互斥,则需要写一个写互斥信号量
-
哲学家进餐
五个哲学家,在一张圆桌上,桌上五支筷子,分别位于哲学家的左右两边。哲学家只有同时拿到两支筷子才能吃。
因为拿到两支才能吃,所以左右两支筷子都设一个信号量。semaphore chopshick[0,...,4]={1,1,1,1,1};//筷子信号量数组 main(){ cobegin philosopy0; philosopy1; philosopy2; philosopy3; philosopy4; coend } philosopyi: { repeat think; wait(chopstick[i]); //申请左边筷子 wait(chopstick[(i+1)mod5]); //右边 eat; signal(chopstick[i]); signal(chopstick[(i+1)mod5]); until false; }
但是当所有人同时申请左右两边的筷子时会陷入死锁
解决办法:当哲学家左右两只筷子都能使用时,才允许拿起筷子。或者最多四人同时拿筷子。或者使用AND信号量philosopyi: { repeat think; Swait(chopstick[i],chopstick[(i+1)mod5]); eat; Ssignal(chopstick[i],chopstick[(i+1)mod5]); until false; }