4.读者 - 写者问题
1.第一个进程上锁,最后一个进程解锁:通过count变量记录该进程当前的数量
2.同类进程不互斥,不同类进程互斥(读者 - 写者问题的最主要特点)
3.写者进程不用判断是否自己是第一个/最后一个进程
1.从南往北的车只要有一辆占据,其余从南到北的车就可以一直使用该路;同理从北往南的车(同类的进程可以共享资源,不同类的进程互斥资源)
2.申明count1变量和count2变量分别用于记录当前P1/P2有几个进程正在使用该临界资源,初始为0
3.在每个进程开始时,通过count是否为0判断自己是不是第一个进程,如果是,则对临界资源进行上锁
4.进行count++,表示自己正在使用临界资源,并在使用完临界资源后进行count--
5.在每个进程结束时,通过count是否为0判断自己是不是最后一个进程,如果是,则对临界资源进行解锁
从左往右的猴子是一类进程,共享绳索这个资源;从右往左的猴子是一类进程,共享绳索;而不同进程间对绳索这一资源是互斥的
1、2、3种不同的录像片对应不同的进程,不同进程互斥访问录像厅(临界资源),统一进程共享录像厅
5.哲学家进餐
特征:只有一类进程,且该类进程只有同时拥有多种资源才能进行
第三个思路最通用:
①将每种资源通过int类型的变量定义(使用变量将资源数量具象化)
②在进程开始运行时,先使用P(MUTEX)操作实现对各种资源进行互斥的访问,目的是逐一判断当前进程所需的各种资源是否满足运行的需要
③如果资源都满足,则拿走这些资源(一口气拿走),然后V(MUTEX),再执行动作
循环条件下:如果只要有一个资源不满足,则V(MUTEX),然后结束此次循环,进行下一次循环(continue);
只进行一次:如果只要有一个资源不满足,则使用goto语句返回函数的第一句重新进行判断(手动实现循环)
其中②实现同一时间只可能有一个进程在进行判断/拿走临界资源
最后完成动作归还资源时也要进行上锁,保证归还动作一气呵成的完成
int a = n; //用int类型表示各种资源 int b = m; semaphore mutex = 1; //互斥信号量,同时仅允许一个进程判断并拿走临界资源 Philosopher () { //进行多次,while while (1) { P(mutex); if (条件满足) { //资源够用,将所需资源分配给该进程,并解锁 a--; b--; 将a和b分配给该进程; V(mutex); } else { //资源不够,则解锁,并进行下一轮循环判断 V(mutex); continue; } 进行主体动作; P(mutex); //完成动作后,归还资源 a++; b++; V(mutex); } } Philosopher () { //进行一次,使用goto start: P(mutex); //上锁 if (条件满足) { a--; b--; 将a和b分配给该进程; V(mutex); } else { V(mutex); goto start; } 进行主体动作; P(mutex); //上锁 a++; //归还a资源 b++; //归还b资源 V(mutex); //解锁 }
①变量wan表示剩余碗的数量
数组a中a[i] = 1表示第i个哲学家的左手有筷子,a[(i + 1) % n] = 1表示第i个哲学家的右有筷子(需要对右手边的筷子进行取模操作,第n个哲学家的右手边筷子就是第1个哲学家左手的筷子)
②P(mutex)实现对临界资源的上锁
③判断wan变量和左右筷子是否都在,任意条件不满足则V(mutex)(解锁)并结束循环;条件都满足时,则对这两个变量进行 - 1操作,表示取出该临界资源
④V(mutex)实现对临界资源的解锁
⑥进餐(即自己的主体动作)
⑦归还拥有的临界资源
另一个哲学家进行上锁并判断检查当前是否有4个盆和1个座位,再去完成打饭和坐落(主体动作)
①解法一:信号量
②解法二:int类型变量(此时每个干饭人只进行一次,不能使用continue)
int seat = m; //剩余m个座位 int plate = n; //剩余n个盘子 semaphore mutex = 1; //互斥信号量 Eatman () { start: 进入餐厅; P(mutex); if (seat >= 4 && plate >= 1) { //满足进餐条件,取走资源,并解锁 seat -=4; plate--; V(mutex); } else { //不满足进餐资源,解锁,并重新判断 V(mutex); goto start; } 干饭; P(mutex); plate += 4; //干饭结束归还资源 seat++; V(mutex); }
6.读者 - 写者(写优先)
semaphore read = 1; //读上锁 semaphore write = 1; //写上锁 semaphore rmutex = 1; //互斥访问readCount semaphore wmutex = 1; //互斥访问writeCount int readCount = 0, writeCount = 0; //分别记录当前的读者/写者数量 //每个读者进程需要通过read信号量判断当前读者是否可进入临界区 //可以进入临界区,则检查自己是否是第一个进入临界区的读者进程 //如果是第一个读者进程,则通过P(write),写上锁,阻塞写者进程 //如果是最后一个读者,则通过V(wrtie),写解锁,唤醒写者进程 Reader(){ while (1) { P(read); //读上锁,即判断读者当前是否可以进入临界区 P(rmutex); //互斥访问readCount变量,上锁 readCount++; //读者数量 + 1 //判断是否为第一个读者进程,若是则写上锁 if (readCount == 1) P(write); V(rmutex); //解锁 V(read); //读解锁 读者读; P(rmutex); //互斥访问readCount变量,上锁 readCount--; //读者数量 - 1 //判断是否为最后一个读者进程,若是则写解锁 if (readCount == 0) V(write); V(rmutex); //解锁 }//while } //每个写者进程需要判断自己是否为第一个写者进程 //如果是,则P(read),读上锁,阻塞后续出现的读者进程,即实现写优先 //最后一个写者进程负责V(read),读解锁,唤醒读者进程 //写者 - 写者,读者 - 写者通过写上锁实现互斥的访问临界区 Writer(){ while(1) { P(wmutex); //互斥访问writeCount变量,上锁 writeCount++; //写者数量 + 1 //判断是否为第一个写者进程,如果是则读上锁,实现写优先 if (writeCount == 1) P(read); V(wmutex); //解锁 P(write); //实现读者-写者互斥,写者-写者互斥 写者写 V(write); P(wmutex); //互斥访问writeCount变量,上锁 writeCount--; //判断是否为最后一个写者进程,如果是则读解锁 if (writeCount == 0) V(read); V(wmutex); }//while }
7.读者 - 写者(读写公平)
semaphore lock = 1; //实现读着 - 写者、写者 - 写者对临界区的互斥访问 int readCount = 0; //记录当前读者数量 semaphore rmutex = 1; //实现对readCount变量的互斥访问 semaphore queue = 1; //读写队列,实现读写公平 //所有读者/写者都同一到队列中排队 //当临界区中有多个读者或者一个写者时,Queue = 0,写者到达,则阻塞在队首 //当临界区中没有进程时,Queue = 1,写者到达则直接访问 writer(){ while(1) { P(queue); //排队 P(lock); //检查是否可以进入临界区,可以则进入并上锁,不能则阻塞 //如果可以执行V(Queue),则说明已经执行P(lock),即已经进入临界区 //而有P(lock)存在,唤醒队首元素并不影响互斥访问临界区 V(Queue); //唤醒下一个队首进程 写者写; V(lock); //退出临界区,解锁 }//while } //只有第一个读者进程执行P(lock),即判断临界区是否上锁/对临界区上锁 //其余的读者进程在readCount不为1时,从队列中被唤醒后,直接进入临界区 //因此,连续的读者进程访问临界区将不会阻塞 //而写者进程将会因第一个读者进程的P(lock)而被阻塞,从而实现读者 - 写者互斥 //最后一个读者进程实现对临界区的解锁 reader(){ while(1) { P(queue); //排队 P(rmutex); //互斥访问readCount readCount++; //读者数量 + 1 if(readCount == 1) P(lock); //自己是第一个读者进程,临界区上锁 V(rmutex); V(queue); //唤醒队首进程 读者读; P(rmutex); //互斥访问readCount readCount--; //读者数量 - 1 if (readCount == 0) V(lock); //自己是最后一个读者进程,临界区解锁 V(rmutex); }//while }