操作系统 经典同步问题
生产者—消费者问题
-
问题描述
一组生产者进程和一组消费者进程共享一个初始为空、大小为 n 的缓冲区,只有缓冲区没满时,生产者才把消息放入缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中读取消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。
-
问题分析
-
关系分析:
生产者和消费者对缓冲区的访问属于互斥关系,而针对“消息”则生产者和消费者属于协作关系,只有生产者生产了消息,消费者才能使用消息,因此又是同步关系。
-
思路:
两组进程存在互斥和同步关系,也就是要解决互斥和同步PV的操作的位置。
-
信号量设置:
设置一个 mutex 为互斥信号量,用于控制互斥访问缓冲池,初值设为 1;
信号量 full 用于记录当前缓冲池中的“满”缓冲区数,初值为 0;
信号量 empty 用于记录当前缓冲池中“空”的缓冲区数,初值为 n;
-
-
进程描述
seamphore mutex=1; //临界区互斥信号量 seamphore empty=n; //空闲缓冲区 seamphore full=0; // 缓冲区初始化为空 producer(){ // 生产者进程 while(1){ produce an item in nextp; // 生产数据 P(empty);(用什么,p一下) // 获取空缓冲区单元 P(mutex);(互斥夹紧) // 进入临界区 add nextp to buffer; (行为) //将数据放入缓冲区 V(mutex);(互斥夹紧) // 离开临界区,释放互斥信号量 V(full);(提供什么,V一下) //满缓冲区数加1 } } consumer(){ //消费者进程 while(1){ P(full); //获取满缓冲区单元 P(mutex); //进入临界区 remove an item from buffer; // 从缓冲区取出数据 V(mutex); // 离开临界区,释放互斥信号量 V(empty); // 空缓冲区数加 1 consume the item; //消费数据 } }
-
Note
- 两个进程中两个P操作顺序不能颠倒,颠倒可能发生以下情况:empty=0,mutex=1时producer执行P(mutex)进入临界区后P(empty)进行阻塞,此时consumer执行P(mutex)也阻塞,从而导致无休止的等待
- 两个进程中两个V操作顺序可以颠倒
生产者—消费者问题(多个生产者和消费者)
-
问题描述
桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可以向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。
-
问题分析
-
关系分析:
爸爸和妈妈是互斥关系,爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,儿子和女儿之间没有互斥和同步关系,因为他们是选择条件执行。
-
思路:
这里一共由4个进程,可抽象为两个生产者和两个消费者被连接到大小为1的缓冲区上。
-
信号量设置:
信号量 plate 表示互斥信号量,用于确定是否可以往盘子中放水果,初值为 1 表示允许放入一个;
信号量 apple 表示盘中是否还有苹果,初值为 0表示没有不许取;
信号量orange 表示盘中是否有橘子,初值同样为 0,orange=1 表示盘子中由橘子允许取。
-
-
进程描述
semapore plate=1,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 aplle from the plate; V(plate); eat the apple; } }
-
Note:此时plate=1,无需设置mutex,但是仍然存在互斥关系
读者-写者问题
-
问题描述
有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程同时访问共享数据时则可能导致数据不一致的错误,因此:
① 允许多个读者可以同时对文件执行读操作;
② 只允许一个写者往文件中写信息;
③ 任一写者在完成写操作之前不允许其他读者进程或写者工作;
④ 写者执行写操作前,应让已有的读者和写者全部退出。 -
问题分析
-
关系分析
读者和写者互斥的,写者和写者互斥,读者和读者之间不互斥。
-
思路
两个进程,读者和写者。由于写者和其他进程都互斥,因此可用互斥信号量的P操作、V操作解决;读者则较为复杂,与写者互斥的同时,又要与其他读者同步,需要一个计数器用于判断当前是否有读者读文件:当有读者时,写者不能写文件,此时读者一直占用文件直到退出,写者才可以写文件;同时,不同读者对计数器的访问也是互斥的。
-
信号量设置
设置 count 信号量为计数器,初值为 0;
mutex 为互斥信号量,用于保护更新count 变量时的互斥;
互斥信号量 rw 用于保证读者和写者互斥访问。
-
-
进程描述(读进程优先)
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++; V(mutex); // 释放互斥变量 count reading; P(mutex); count--; if(count==0) // 当最后一个读进程读完共享文件 V(rw); // 允许写进程写 V(mutex); } } //当读进程未完成时,写进程会阻塞,当写进程一直得不到执行,会导致写进程饿死
读写公平
int count=0; semaphore mutex=1; 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++; V(mutex); // 释放互斥变量 count V(w); reading; P(mutex); count--; if(count==0) // 当最后一个读进程读完共享文件 V(rw); // 允许写进程写 V(mutex); } }
哲学家进餐问题
-
问题描述
一张圆桌上坐着5名哲学家,每两名哲学家之间的桌子上摆着一根筷子,两根筷子之间是一碗米饭。哲学家倾注毕生精力于思考和进餐,哲学家思考时不影响其他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子——一根一根地拿起。若筷子已在他人手上,则需要等待。饥饿地哲学家只有同时拿到了两根筷子才能开始进餐,进餐完毕,放下筷子继续思考。
-
问题分析
-
关系分析
5 名哲学家与左右邻座对其中间的筷子的访问时互斥关系。
-
思路
5 个哲学家对应5 个进程,问题解决的关键就是如何让一名哲学家拿到左右两根筷子而不造成死锁或饥饿现象。
解决方法有两个:
1.让他们同时拿两根筷子;
2.是对每名哲学家的动作制定规则,避免饥饿或死锁现象的发生。
-
信号量设置
互斥信号量数组 chopstick[5]={1,1,1,1,1},用于对 5 个筷子的互斥访问;哲学家编号顺序:0~4,哲学家 I 左边筷子的编号为 i,哲学家右边筷子的编号为(i+1)%5。
-
-
进程描述
//该方案不可行 semaphore chopstick[5]={1,1,1,1,1}; Pi(){ do{ P(chopstick[i]); //取左边筷子 P(chopstick[(i+1)%5]);// 取右边筷子 eat; V(chopstick[i]); //放回左边筷子 V(chopstick[(i+1)%5]);// 放回右边筷子 think; }while(1); } //当五个人依次P(chopstick[i])时会导致死锁
解决死锁
semaphore chopstick[5]={1,1,1,1,1}; semaphore mutex=1;//取筷子时互斥 Pi(){ do{ P(mutex); // 在取筷子前获得互斥量 P(chopstick[i]); //取左边筷子 P(chopstick[(i+1)%5]);// 取右边筷子 V(mutex); // 释放取筷子的信号量 eat; V(chopstick[i]); //放回左边筷子 V(chopstick[(i+1)%5]);// 放回右边筷子 think; }while(1); }
吸烟者问题
-
问题描述
假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但要卷起一支烟,抽烟者需要三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉已完成,此时供应者就会将另外两种材料放到桌子上,循环反复如此。
-
问题分析
-
关系分析
供应者与三个抽烟者分别是同步关系。由于供应者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥。
-
思路
显然有4个进程,供应者作为生产者向三个抽烟者提供材料。
-
信号量设置
信号量 offer1,offer2,offer3 分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源;
信号量 finish 用于互斥进行抽烟动作。
-
-
进程描述
int count=0; semaphore offer1=0; semaphore offer2=0; semaphore offer3=0; semaphore finish=0; process P1(){ while(1){ count++; count=count%3; if(count==0) V(offer1); // 提供烟草和纸 else if(count==1) V(offer2); // 提供烟草和胶水 else V(offer3); //提供纸和胶水 put on ; // 将材料放在桌子上 P(finish); } } process P2(){ while(1){ P(offer3); working; // 拿起纸和胶水,卷成烟,抽掉 V(finish); } } process P3(){ while(1){ P(offer2); working; // 拿起烟草和胶水,卷成烟,抽掉 V(finish); } } process P4(){ while(1){ P(offer1); working; // 拿起纸和烟草,卷成烟,抽掉 V(finish); } }