对于进程这块来说,同步问题算是一个大头了,我也是看了好几遍,真的服
王道里面这样说:“如果一个操作想要取得资源你就在它前面P一下那个资源,如果一个操作产生了一个资源后,你就要在它后面V一下”;
这个确实,
申请资源之前看看,有没有资源取得,有的话自然进入下一步,没有的话,阻塞了;
生产了资源的,生产了之后V一下,这个时候上面阻塞的那个P就可以继续执行下去了(这也是所谓的唤醒);
几个经典的问题
- 生产者/消费者
这里面就有两个进程,producer()、consumer();
一个产生资源,一个取资源,还有一个用来放资源的缓冲区n个空间;
semaphore mutex=1;
semaphore empty=n;
semaphore full=0;
producer(){
while(1){
p(empty);
p(mutex);
...produce
v(mutex);
v(full);
}
}
consumer(){
while(1){
p(full);
p(mutex);
...consume;
v(mutex);
v(empty);
}
}
这个里面为啥要用互斥呢!mutex
因为对缓冲区的访问得互斥,同一时刻只可以有一个进程对缓冲区操作;
同步嘛
因为每次生产总要看看缓冲区满没满吧,缓冲区满了就不能在放了(阻塞),或者缓冲区是不是被取空了,空了也不能再取了(阻塞);
- 多角色消费者问题
生产者 父亲/母亲 消费者 儿子/女儿
父亲放苹果/母亲放橘子 儿子吃橘子/女儿吃苹果
这里涉及到四个进程,父亲/母亲/儿子/女儿 ,同时盘子作为一个互斥访问的临界区
semaphore plate=1;
semaphore apple=0;
semaphore orange=0;
dad(){
while(1){
prepare an apple;
p(plate);
put an apple;
v(apple);
}
}
mom(){
while(1){
prepare an orange;
p(plate);
put an orange;
v(orange);
}
}
son(){
while(1){
p(orange);
take the apple;
v(plate);
eat;
}
}
daughter(){
while(1){
p(apple);
take the apple;
v(plate);
eat;
}
}
这里面的情况其实比较简单因为儿子和女儿使用的资源不同所以他们之间不存在竞争资源的问题,而作为放水果的父亲母亲对盘子的访问就要互斥进行;
- 读者写者问题
总共有两组进程,读者/写者,对同一个文件进行读写,要求有读者在读的时候,不可以进行写操作,但可以有另外的读者同时对该文件读取,也就是说,读写进程不可并发,读进程之间可以并发。
第一种对于写者并不友好的方式
semaphore count=0;
semaphore mutex=1;
semaphore rw=1;
writer(){
while(1){
p(rw);
write the file;
v(rw);
}
}
reader(){
while(1){
p(mutex);
if(count==0)
p(rw);
count++;
v(mutex);
read the file;
p(mutex);
count--;
if(count==0)
v(rw);
v(mutex);
}
}
这种情况下,很容易出现有一个稳定读者而使写进程始终都不到处理机,从而出现饥饿现象。下面是一个相对来说比较公平的改进,在这个基础之上加上对访问顺序的控制,
即如果进程有写请求后会将此写进程之后的所有读请求阻塞,使之排在该写进程后面,说白了,所有人 都给我按顺序排好队,没有人有特权;
semaphore w=1;
semaphore rw=1;
semaphore mutex=1;
semaphore count=0;
writer(){
while(1){
p(w);
p(rw);
write the file;
v(rw);
v(w);
}
}
reader(){
while(1){
p(w);
p(mutex);
if(count==0)
p(rw);
count++;
v(mutex);
v(w);
read the file;
p(mutex);
count--;
if(count==0)
v(rw);
v(mutex);
}
}
这里面用到的信号量w,是用来防插队的,防止后来的读者抢占在写着前面,使写着得不到处理,这里面用来记录读者数量个的计数器是一定要互斥访问的,不然在读进程的并发执行过程中就会出现错误。
- 哲学家进餐问题
只有当哲学家拿到两个筷子的时候才可以用餐,这个问题就是涉及到多个进程(哲学家)问题,每一个哲学家即一个进程,若采取每个哲学家先取左手边筷子则很容易出现每个哲学家都拿齐了自己的左手边的筷子而导致每个人都有一个筷子并被阻塞,唔认得道第二支筷子而陷入了死锁。
现以五个哲学家为例,解决此问题的一个方法就是让哲学家一次同时拿起两个筷子,如果不能拿到两个,就放弃,也就是说每个哲学家进程在拿两支筷子的过程是互斥的。
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex=1;
Pi(){
while(1){
p(mutex);
p(chopstick[i]);
p(chopstick[(i+1)%5]);
v(mutex);
take the meal;
v(chopstick[i]);
v(chopstick[(i+1)%5]);
}
}
- 吸烟者问题
这个问题里面有一个提供者,提供三种材料,但每次只向盘子中放其中两种材料
三个吸烟者,每个人都只有三种材料之中的一种且都各不相同;
所以这是一个同步的问题
int random;
semaphore plate=1;
semaphore offer0=0;
semaphore offer1=0;
semaphore offer2=0
producer(){
while(1){
int random=random%3;
if(random==0)
v(offer0);
esle if(random==1){
v(offer1);
else if(random==2)
v(offer2);
p(plate);
}
}
smoker0(){
while(1){
p(offer0);
get the smoke;
v(plate);
}
}
smoker1(){
while(1){
p(offer1);
get the smoke;
v(plate);
}
}
smoker2(){
while(1){
p(offer2);
get the smoke;
v(plate);
}
}
对于一个信号量K 当它大于零时表示还有资源剩余,当其为负数时表示系统中等待资源的进程数目|K|
注意:若遇到类似生产者消费者问题中的同一生产者对应多个不同消费者时,要记得分情况来写,在生产进程中用信号量将多个消费进程分开即通过内部的判断 来决定执行那个消费者的V,这是这类问题的普遍解法。
在写V操作的时候要清楚了解顺序的问题,这是一个值得仔细思考的地方。
在做PV操作类的题目首先要明确,临界区是哪块,通常为共享缓冲区,对着干临界区是否要互斥,这个经常忘记,在写了同步之后就把互斥忘记了。注意点