进程同步——经典的同步问题
本文为博主原创文章,未经博主允许不得转载 http://www.cnblogs.com/kiplove/p/6745335.html
涉及进程同步的一些概念:
互斥与同步:
临界资源(临界区):指一次只能允许一个进程使用的共享资源称为临界资源;
同步:指为完成某种任务而建立的两个和多个进程,这些进程在合作的过程中需要协调工作次序进行有序的访问而出现等待所产生的制约关系。
互斥:指两个或多个进程访问临界资源时只能一个进程访问,其他进程等待的一种相互制约的关系。
信号量与互斥量:
信号量:本身是一个计数器,使用P,V两个操作来实现计数的减与加,当计数不大于0时,则进程进入睡眠状态,它用于为多个进程提供共享数据对象的访问。
互斥量:如果信号量只存在两个状态,那就不需要计数了,可以简化为加锁与解锁两个功能,这就是互斥量。
一、生产者与消费者问题
问题描述:一组生产者进程和一组消费者进程共享一块初始为空,大小确定的缓冲区,只有当缓冲区为满时,生产者进程才可以把信息放入缓冲区,否则就要等待;只有缓存区不为空时,消费者进程才能从中取出消息,否则就要等待。缓冲区一次只能一个进程访问(临界资源)。
问题分析:生产者与消费者进程对缓冲区的访问是互斥关系,而生产者与消费者本身又存在同步关系,即必须生成之后才能消费。因而对于缓冲区的访问设置一个互斥量,再设置两个信号量一个记录空闲缓冲区单元,一个记录满缓冲区单元来实现生产者与消费者的同步。
问题解决:伪代码实现
semaphore mutex=1; semaphore full=0; //满缓冲区单元 semaphore empty=N; //空闲缓冲区单元 prodecer() { while(1) { P(empty); P(mutex); add_source++; V(mutex); V(full); } } consumer() { while(1) { P(full); P(mutex); add_source--; V(mutex); V(empty); } }
二、读者与写者问题
问题描述:有读者与写者两个并发进程共享一个数据,两个或以上的读进程可以访问数据,但是一个写者进程访问数据与其他进程都互斥。
问题分析:读者与写者是互斥关系,写者与写者是互斥关系,读者与读者是同步关系。因而需要一个互斥量实现读与写和写与写互斥,一个读者的访问计数和实现对计数的互斥。
问题解决:三种伪代码实现
1、读者优先
读者优先,只要有读者源源不断,写者就得不到资源。容易造成写者饥饿。
1 //读者优先 2 3 int count=0; 4 semaphore mutex=1; //读者计数锁 5 semaphore rw=1; //资源访问锁 6 7 writer() 8 { 9 while(1) 10 { 11 P(rw); 12 writing sth; 13 V(rw); 14 } 15 } 16 17 reader() 18 { 19 while(1) 20 { 21 P(mutex); 22 if(count==0) 23 P(rw); 24 count++; 25 V(mutex); 26 reading sth; 27 P(mutex); 28 count--; 29 if(count==0) 30 V(rw); 31 V(mutex); 32 } 33 }
2、读写公平
读者与写者公平抢占资源,但是只要之前已经排队的读者,就算写者获取的资源,也要等待所有等待的读者进程结束。
1 //读写公平 2 int count=0; 3 semaphore mutex=1; //读者计数锁 4 semaphore rw=1; //资源访问锁 5 semaphore w=1; //读写公平抢占锁 6 writer() 7 { 8 while(1) 9 { 10 P(w); 11 P(rw); 12 writing sth; 13 V(rw); 14 V(w); 15 } 16 } 17 18 reader() 19 { 20 while(1) 21 { 22 P(w); 23 P(mutex); 24 if(count==0) 25 P(rw); 26 count++; 27 V(mutex); 28 V(w); 29 reading sth; 30 P(mutex); 31 count--; 32 if(count==0) 33 V(rw); 34 V(mutex); 35 } 36 }
3、写者优先
写者优先,只要写者源源不断,读者就得不到资源,但是在这之前已经排队的的读者进程依然可以优先获得资源,在这之后则等待所有写者进程的结束。这种也易造成读者饥饿。
1 //写者优先 2 int write_count=0; //写计数 3 int count=0; //读计数 4 semaphore w_mutex=1; //读计数时锁 5 semaphore r_mutex=1; //写计数时锁 6 semaphore rw=1; //写优先锁 7 semaphore source=1; //资源访问锁 8 9 writer() 10 { 11 while(1) 12 { 13 P(w_mutux); 14 if(write_count==0) 15 P(rw); //获得则只要有写进程进来就不释放 16 write_count++; 17 V(w_mutux) 18 19 P(resouce); //写时互斥必须加资源独占的锁 20 writing sth; 21 V(resouce); 22 23 P(w_mutux); 24 write_count--; 25 if(write_count==0) 26 V(rw); 27 V(w_mutux); 28 } 29 } 30 31 reader() 32 { 33 while(1) 34 { 35 P(rw); //使用了立即释放 36 P(r_mutex); 37 if(count==0) 38 P(resouce); 39 count++; 40 V(r_mutex); 41 V(rw); 42 43 reading sth; 44 45 P(r_mutex); 46 count--; 47 if(count==0) 48 V(resouce); 49 V(r_mutex); 50 } 51 }
三、哲学家就餐问题
问题描述:一张圆桌上坐着五名哲学家,每两名哲学家之间的桌子摆一根筷子,哲学家只有同时拿起左右两根筷子时才可以用餐,用餐完了筷子放回原处。
问题分析:这里五名哲学家就是五个进程,五根筷子是需要获取的资源。可以定义互斥数组用于表示五根筷子的互斥访问,为了防止哲学家个取一根筷子出现死锁,需要添加一定的限制条件。一种方法是限制仅当哲学家左右筷子均可以用时,才拿起筷子,这里需要一个互斥量来限制获取筷子不会出现竞争。
问题解决:一次仅能一个哲学家拿起筷子,效率比较低。
1 semaphore chopstick[5]={1,1,1,1,1}; 2 semaphore mutex=1; 3 pi() 4 { 5 while(1) 6 { 7 P(mutex); 8 P(chopstick[i]); 9 P(chopstick[(i+1)%5]); 10 V(mutex); 11 12 eating; 13 14 V(chopstick[i]); 15 V(chopstick[(i+1)%5]); 16 } 17 }
补充内容
死锁:如果一个进程集合中的每个进程都在等待只能由该进程集合中的其他进程才能引发的事件,那么该进程集合就是死锁的。
死锁的条件(四个同时满足):
(1)互斥:每个资源要么已经分配给一个进程,要么就是可用的;
(2)占有和等待:已经得到的某个资源的进程请求新的资源;
(3)不可抢占:已经分配的资源不能强制被抢占,只能进程自己显示的释放;
(4)环路等待:存在一种进程资源的循环等待链。
死锁的处理策略:
(1)死锁预防:破坏死锁的四个条件之一
破环互斥条件:允许资源共享
破环占有和等待条件:采用预先静态分配
不可抢占:请求新资源得不到时,释放已经保持占有的资源,待以后重新申请
环路等待:采用顺序资源分配法
(2)死锁避免:死锁避免事先预防策略,但是是采用资源动态分配的过程中,防止系统进入不安全状态,以避免死锁。
银行家算法:可利用资源矢量Available,请求矢量Request
最大需求矩阵Max,分配矩阵Allocation,需求矩阵Need
通过Need=Max-Allocation获得每个进程需要的各类资源数Need矩阵
一般每个进程请求矢量应该小于等于Need的值
试探分配:Available=Avaliable-Request
Allocate相对应的项=Allocate相对应的项+Request
Need相对应的项=Need相对应的项-Request
安全性算法:检查资源分配后,系统是否属于安全状态,如果安全才正式分配资源,否则作废。一般通过安全性算法推算一个安全序列(核心)。
(3)死锁检测与解除:
检测死锁:利用死锁原理化简资源分配图检测死锁的存在
死锁解除:资源剥夺、撤销进程、进程回退