基于信号量的进程同步与互斥(2)
基于信号量的进程同步与互斥(2)
1. 三个进程P1、P2、P3 互斥使用一个包含N(N>0)个单元的缓冲区。P1 每次用produce()生成一个正整数并用put()送入缓冲区某一个空单元中;P2 每次用getodd()从该缓冲区中取出一个奇数并用countodd()统计奇数个数;P3 每次用 geteven()从该缓冲区中取出一个偶数并用counteven()统计偶数个数。请用信号量机制实现这三个进程的同步与互斥活动,并说明所定义的信号量的含义。
本题主要考察生产者-消费者的变形,一个生产者对应两个消费者,注意要写cobegin-coend等语句
(1)缓冲区是一互斥资源,因此设互斥信号量mutex;
(2)同步问题:P1、P2因为奇数的放置与取用而同步,设同步信号量odd;P1、P3因为偶数的放置与取用而同步,设同步信号量even;P1、P2、P3因为共享缓冲区,设同步信号量empty。
semaphore mutex = 1, odd = 0, even = 0, empty = N;
main()
cobegin{
Process P1
while (true) {
number = produce();
P(empty);
P(mutex);
put();
V(mutex);
if number % 2 == 0
V(even);
else
V(odd);
}
Process P2
while (true) {
P(odd);
P(mutex);
getodd();
V(mutex);
V(empty);
countodd();
}
Process P3
while (true) {
P(even);
P(mutex);
geteven();
V(mutex);
V(empty);
counteven();
}
}coend
2. 一个野人部落从一个大锅中一起吃炖肉,这个大锅一次可以存放 M 人份的炖肉。当野人们想吃的时候,如果锅中不空,他们就自助着从大锅中吃肉。如果大锅空了,他们就叫醒厨师,等待厨师再做一锅肉。
野人线程未同步的代码如下:
while (true){
getServingFromPot();
eat();
}
厨师线程未同步的代码如下:
while (true) {
putServingsInPot(M);
}
同步的要求是:当大锅空的时候,野人不能够调用getServingFromPot();仅当大锅为空的时候,大厨才能够调用putServingsInPot()
问题:请写出满足同步要求的野人线程和厨师线程的同步原语。
servings = 0 //表示锅中肉的份数
mutex = Semaphore(1) //互斥量保护serving
emptyPot = Semaphore(0) //pot为空
fullPot = Semaphore(0) //pot为满
厨师的同步代码:
while (true) {
P(emptyPot)
putServingsInPot(M)
V(fullPot)
}
野人的同步代码
while (true) {
P(mutex)
if (servings == 0) {
V(emptyPot)
P(fullPot)
servings = M
}
servings -= 1
getServingFromPot()
V(mutex)
eat()
}
3. 系统中有多个生产者进程和消费者进程,共享用一个可以存 1000 个产品的缓冲区(初始为空),当缓冲区为未满时,生产者进程可以放入一件其生产的产品,否则等待;当缓冲区为未空时,消费者进程可以取走一件产品,否则等待。要求一个消费者进程从缓冲区连续取出 10 件产品后,其他消费者进程才可以取产品,请用信号量 P,V 操作实现进程间的互斥和同步,要求写出完整的过程;并指出所用信号量的含义和初值。
本题是一个生产者-消费者的变型,本题是多个生产者-多个消费者类型,生产者和消费者之间、生产者和生产者之间、消费者和消费者之间要互斥访问缓冲区,并且本题的消费者一次需取走10件产品,如果没有它会等待,而不是等到有了10件产品后,才进行取走操作。
本题的缓冲区B可描述为 buffer array[1000];
(1)生产者之间,生产者消费者之间设互斥信号量mutex1,消费者之间设互斥信号量mutex2.
(2)上述进程的同步问题,需设置3个信号量,其中empty对应空闲的缓冲单元,初值为1000;full对应缓冲区中待取走的产品数,初值为0;另外,还需定义2个整形变量in、out,分别用来指示下一个可存放产品的缓冲单元、下一个可取走的缓冲单元,它们的初值均为0。
buffer array[1000]; //存放产品的缓冲区
buffer nextp; //用于临时存放生产者生产的产品
buffer nextc[10]; //用于临时存放消费者取出的产品
semaphore empty = 1000; //空缓冲区的数目
semaphore full = 0; //满缓冲区的数目
semaphore mutex1 = 1; //用于生产者之间的互斥,以及生产者消费者互斥
semaphore mutex2 = 1; //用于消费者之间的互斥
int in = 0; //指示生产者的存位置
int out = 0; //指示消费者的取位置
Producer() { //生产者进程
Produce an item put in nextp; //生产一个产品,存在临时缓冲区
P(empty); //申请一个空缓冲区
P(mutex1); //生产者申请使用缓冲区
array[in] = nextp; //将产品存入缓冲区
in = (in + 1) % 1000; //指针后移
V(mutex1); //生产者缓冲区使用完毕,释放互斥信号量
V(full); //增加一个满缓冲区
}
Consumer() { //消费者进程
P(mutex2); //消费者申请使用缓冲区
for (int i = 0; i < 10; i++) { //一个消费者进程需从缓冲区连续取走10件产品
P(full); //申请一个满缓冲区
P(mutex1); //互斥生产者
nextc[i] = array[out]; //将产品取出,存于临时缓冲区
out = (out + 1) % 1000; //指针后移
V(mutex1); //解除生产者互斥
V(empty); //增加一个空缓冲区
}
V(mutex2); //消费者缓冲区使用完毕,释放互斥信号量
Consume the items in nextc; //消费掉这10个产品
}
4. 读者——写者问题(写者优先)
信号量定义
readSwitch = Lightswitch()
writeSwitch = Lightswitch()
noReaders = Semaphore(1)
noWriters = Semaphore(1)
class Lightswitch:
def __init__(self):
self.counter = 0 //封装计数器和互斥锁
self.mutex = Semaphore(1)
def lock(self, semaphore): //信号量是参数
self.mutex.wait()
self.counter += 1
if self.counter == 1:
semaphore.wait()
self.mutex.signal()
def unlock(self, semaphore):
self.mutex.wait()
self.counter -= 1
if self.counter == 0:
semaphore.signal()
self.mutex.signal()
最初信号量都是解锁态。如果reader在临界区,会给noWriter上锁,但是不会给noReader上锁。如果writer到来,会给noReader加锁,会让后续读者排队在noReader。当最后一个读者离开,他会signal noWriter,这时写者可以进入。
当写者进入临界区,他同时拿着noReader和noWriter两个锁。一方面,其他读者和写者不能同时访问临界区。另一方面,writeSwitch允许其他写者通过,并在noWriter等待。但是读者只能在noReader等待。这样,所有排队的写者能够通过临界区,而不需要signal noReader。当最后一个写者离开,noReader才解锁。
Reader:
noReaders.wait()
readSwitch.lock(noWriters)
noReaders.signal()
# critical section for readers
readSwitch.unlock(noWriters)
Writer:
writeSwitch.lock(noReaders)
noWriters.wait()
# critical section for writers
noWriters.signal()
writeSwitch.unlock(noReaders)
semaphore noReaders = 1
semaphore noWriters = 1
int readers = 0
semaphore readers_mutex = 1
int writers = 0
semaphore writers_mutex = 1
semaphore
main()
cobegin{
Process Reader
P(noReaders);
P(readers_mutex);
readers += 1;
if (readers == 1) {
P(noWriters);
}
V(readers_mutex);
V(noReaders);
#critical section for readers
P(readers_mutex);
readers -= 1;
if (readers == 0) {
V(noReaders);
}
V(readers_mutex);
Process Writer
P(writers_mutex);
writers += 1;
if (writers == 1) {
P(noReaders);
}
V(writers_mutex);
P(noWriters);
# critical section for writers
V(noWriters);
P(writers_mutex);
writers -= 1;
if (writers == 0) {
V(noReaders);
}
V(writers_mutex);
}coend
5. 寿司店问题。假设一个寿司店有 5 个座位,如果你到达的时候有一个空座位,你可以立刻就坐。但是如果你到达的时候 5 个座位都是满的有人已经就坐,这就意味着这些人都是一起来吃饭的,那么你需要等待所有的人一起离开才能就坐。编写同步原语,实现这个场景的约束。
eating = waiting = 0
mutex = Semaphore(1)
block = Semaphore(0)
must_wait = False
P(mutex)
if must_wait: //需要等待所有人离开
waiting += 1
V(mutex)
P(block) //在block上睡眠
else: //可以直接吃,如果就坐后满了,后续需要等待
eating += 1
must_wait = (eating == 5)
V(mutex)
# eat sushi
P(mutex)
eating -= 1 //吃完了
if eating == 0: //如果最后一个顾客,可唤醒block上等待的顾客
n = min(5, waiting) //最多5个顾客可以吃
waiting -= n
eating += n
must_wait = (eating == 5) //reset must_wait
for (int i = 0; i < n; i++) {
V(block)
}
V(mutex)
6. 搜索-插入-删除问题。三个线程对一个单链表进行并发的访问,分别进行搜索、插入和删除。搜索线程仅仅读取链表,因此多个搜索线程可以并发。插入线程把数据项插入到链表最后的位置;多个插入线程必须互斥防止同时执行插入操作。但是,一个插入线程可以和多个搜索线程并发执行。最后,删除线程可以从链表中任何一个位置删除数据。一次只能有一个删除线程执行;删除线程之间,删除线程和搜索线程,删除线程和插入线程都不能同时执行。
semaphore noSearch = 1; //表示临界区无搜索进程
semaphore noInsert = 1; //表示临界区无插入进程
semaphore iMutex = 1; //用于插入进程之间的互斥
int insertNum = 0; //表示插入进程的数目
semaphore mutexInsertNum = 1; //保护对insertNum的互斥访问
int searchNum = 0; //表示搜索进程的数目
semaphore mutexSearchNum = 1; //保护对searchNum的互斥访问
Process Delete
P(noSearch);
P(noInsert);
delete();
V(noInsert);
V(noSearch);
Process Insert
P(mutexInsertNum);
insertNum++;
if (insertNum == 1) {
P(noInsert);
}
V(mutexInsertNum);
P(iMutex);
insert();
V(iMutex);
P(mutexInsertNum);
insertNum--;
if (insertNum == 0) {
V(noInsert);
}
V(mutexInsertNum);
Process Search
P(mutexSearchNum);
searchNum++;
if (searchNum == 1) {
P(noSearch);
}
V(mutexSearchNum);
search();
P(mutexSearchNum);
searchNum--;
if (searchNum == 0) {
V(noSearch);
}
V(mutexSearchNum);
以上部分内容引自课件,如有侵权,请及时联系我删除!