生产者消费者、吸烟者、读者写者、哲学家进餐问题、管程

生产者消费者问题

问题描述:系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用(产品:某种数据)
生产者、消费者共享一个初始为空、大小为n的缓冲区
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待(同步关系:缓冲区满要等待消费者取走产品)
只有缓冲区不空时,消费者才能从中取出产品,否则必须等待(同步关系:缓冲区空要等待生产者放入产品)
缓冲区是临界资源,各进程必须互斥访问(互斥关系)

  1. 关系分析:如上括号内容
  2. 整理思路:PV操作的大致顺序。生产者每次要消耗一个缓冲区,生产一个产品;消费者每次要消耗一个产品,释放一个缓冲区;放入和取走需要互斥。三对PV操作,三对信号量

信号量设置

semaphore mutex = 1; // 互斥信号量,用于实现对缓冲区的互斥访问
semaphore empty = n; // 同步信号量,表示空闲缓冲区的数量
semaphore full = 0; // 同步信号量,表示产品的数量

producer(){
    while(1){
        produce a product;
        P(empty);
        P(mutex);
        put the product into buffer;
        V(mutex);
        V(full);
    }
}
consumer(){
    while(1){
        P(full);
        p(mutex);
        get a product from buffer;
        V(mutex);
        V(empty);
        use the product;
    }
}

实现互斥是在同一进程中进行一对PV操作
实现两进程的同步是在其中一个执行P,另一个执行V

能够改变相邻P、V操作的顺序吗

  1. 实现互斥的P操作一定要在实现同步的P操作之后
    如果交换P的顺序,若此时已经放满产品,empty = 0,full = n。由于生产者进程执行P(mutex),mutex变为0,再执行P(empty),由于没有空闲缓冲区,生产者被阻塞,等待消费者消费产品。消费者进程中执行P(mutex),将mutex变为-1,消费者进程被阻塞,等待生产者将mutex解锁。两者一起等待被对方唤醒,陷入死锁
  2. V操作可以交换,因为V操作不会导致进程阻塞

能把生产产品和使用产品放入临界区吗
可以,但是会使得临界区代码量变大,访问临界区需要更长时间,进程的并发度会降低

多生产者多消费者问题

问题描述:桌子上有一个盘子,每次只能向其中放入一个水果,爸爸放苹果,妈妈放橘子,儿子吃橘子,女儿吃苹果,盘子空时,才可放入一个水果,当盘子有自己需要的水果才可以取水果,设计PV操作

大小为1,初始为空的缓冲区
父母为两个生产者进程
儿女为两个消费者进程

  1. 关系分析
    互斥关系:对盘子的访问互斥进行(mutex = 1)
    同步关系:父亲放了女儿吃(apple = 0),母亲放了儿子吃(orange = 0),女儿或儿子吃了父亲母亲才能放(plate = 1,盘子能放水果的数量,缓冲区)
  2. 设置信号量
semaphore mutex = 1;
semaphore apple = 0;
semaphore orange = 0;
semaphore plate = 1;

dad(){
    while(1){
        prepare an apple;
        P(plate);
        P(mutex);
        put the apple into the plate;
        V(mutex);
        V(apple);
    }
}

mom(){
    while(1){
        prepare an orange;
        P(plate);
        P(mutex);
        put the apple into the plate;
        V(mutex);
        V(orange);
    }
}

son(){
    while(1){
        P(orange);
        P(mutex);
        take an orange from the plate;
        V(mutex);
        V(plate);
        eat the orange;
    }
}

daughter(){
    while(1){
        P(apple);
        P(mutex);
        take an apple from the plate;
        V(mutex);
        V(plate);
        eat the apple;
    }
}

可以不要互斥信号量吗
可以的,即使没有mutex也不会出现多个进程同时访问盘子的现象。因为任何时刻,apple、orange、plate三个同步信号量最多只有一个为1,因此任何时刻,最多只有一个进程的P操作不会阻塞
如果plate为2,就可能出现父母同时访问盘子的情况

吸烟者问题

问题描述:假设一个系统有三个抽烟者喝一个供应者,抽烟者不停卷烟并抽掉,需要三种材料:烟草、纸和胶水。三个抽烟者中,第一个有烟草,第二个有纸,第三个供胶水。供应者无限提供三种材料,每次将两种材料放桌子上,拥有另一种材料的抽烟者就卷烟抽烟,并告诉供应者我完成了,供应者又放另外两种材料,让他们轮流吸烟

关系分析
桌子可以看成容量为1的缓冲区,互斥访问(mutex)
有组合一,抽烟者1可以抽烟(同步关系offer1 = 0)
有组合二,吸烟者2可以抽烟(同步关系offer2 = 0)
有组合三,吸烟者3可以抽烟(同步关系offer3 = 0)
完成信号后,放下一个组合(同步关系plate = 1)

semaphore offer1 = 0;
semaphore offer2 = 0;
semaphore offer3 = 0;
semaphore plate = 1;
semaphore mutex = 1;
provider(){
    while(1){
        P(plate);
        P(mutex);
        group1 be put into the plate;
        V(mutex);
        V(offer1);
        P(plate);
        P(mutex);
        group2 be put into the plate;
        V(mutex);
        V(offer2);
        P(plate);
        P(mutex);
        group3 be put into the plate;
        V(mutex);
        V(offer3);
    }
}
smoker1(){
    while(1){
        P(offer1);
        P(mutex);
        take group1 from the plate;
        V(mutex);
        V(plate);
    }
}
smoker2(){
    while(1){
        P(offer2);
        P(mutex);
        take group2 from the plate;
        V(mutex);
        V(plate);
    }
}
smoker3(){
    while(1){
        P(offer1);
        P(mutex);
        take group3 from the plate;
        V(mutex);
        V(plate);
    }
}

plate、offer1-3在同一时刻至多一个为1,所以四个进程至多一个不被阻塞,可以不要mutex
实现随机选一个人吸烟:增加变量i,i = random%3

读者写者问题

问题描述:有读者和写者两组并发进程,共享一个文件,允许多个读者进程同时访问共享数据,只允许一个写者进程访问共享数据,读者进程和写者进程不能同时访问共享数据

关系分析
互斥关系:写进程和写进程,写进程和读进程

semaphore rw = 1; // 用于实现文件的互斥访问,表示当前是否有进程在访问共享文件
int count = 0; // 记录当前有多少个读进程在访问文件、
semaphore mutex = 1; // 用于实现对count的互斥访问
semaphore w = 1; // 用于实现写优先

writer(){
    while(1){
        P(rw);
        write;
        V(rw);
    }
}

// 这个版本如果两个读者并发执行,都判断count = 0,执行P,其中一个会被阻塞
// 原因是count的检查和赋值没有一气呵成,可以增加一个互斥信号量保证对count访问互斥
reader(){
    while(1){
        if(count == 0)
            P(rw);
        count++;
        read;
        count--;
        if(count == 0)
            V(rw);
    }
}

// 改为下面这个版本
// 潜在问题:只要有读进程还在读,写进程就要一直阻塞等待,可能饿死,读进程优先
reader(){
    while(1){
        P(mutex);
        if(count == 0)
            P(rw);
        count++;
        V(mutex);
        read;
        P(mutex);
        count--;
        if(count == 0)
            V(rw);
        V(mutex);
    }
}

// writer和reader实现写优先的版本
// 如果两个读者进程并发执行P(w),读者之间同时读文件不会被影响
// 两个写者并发,一个写者会被阻塞
// 写者和读者,读者会被P(w)阻塞

// 读者和写者和读者2,读者在顺利读文件的时候写者被P(rw)阻塞
// 读者2来的时候,写者执行过P(w),所以读者2被P(w)阻塞
// 直到写者写完执行V(w),读者2才能继续

// 写者1读者写者2,写者1写的时候读者写者2会被阻塞在P(w)
// 由于读者先到达,w的阻塞队列中读者优先被唤醒,写者1执行V(w)后唤醒了读者
// 读者读完才到写者2
// 所以这里的写优先并非真正写优先,而是相对公平的先来先服务
writer(){
    while(1){
        P(w);
        P(rw);
        write;
        V(rw);
        V(w);
    }
}

reader(){
    while(1){
        P(w);
        P(mutex);
        if(count == 0)
            P(rw);
        count++;
        V(mutex);
        V(w);
        read;
        P(mutex);
        count--;
        if(count == 0)
            V(rw);
        V(mutex);
    }
}

重点:计数器count记录当前读进程访问文件数目;为了解决count的检查赋值一气呵成,增加mutex;为了解决写进程饥饿,加入w互斥量实现公平

哲学家进餐问题

问题描述:圆桌上5名哲学家,每两个哲学家之间摆一根筷子,桌子中间为米饭,哲学家思考不影响他人,哲学家饥饿时才试图拿起筷子(一根一根拿,只能拿自己左右手的筷子),如果筷子在别人手里,就等待,同时拿起两根筷子才能进餐,进餐完毕,放下筷子思考

关系分析

  1. 5个哲学家进程,哲学家和相邻的两个哲学家对中间的筷子的访问是互斥的
  2. 如果每个哲学家拿了一根筷子,大家都吃不了饭,造成死锁,如何解决
  3. 定义互斥信号量chopstick[5] = {1,1,1,1,1},哲学家01234,i哲学家左边的筷子是i

如何预防死锁呢

  1. 可以允许最多四个哲学家同时进餐
  2. 可以令奇数号哲学家先拿左边筷子,偶数号先拿右边筷子
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);
        eat hotpot;
        V(chopstick[i]); // 放左
        V(chopstick[(i+1)%5]; // 放右
        think;
    }
}

012的顺序,则0吃饭,12都吃不了饭,要等0放下筷子,1才能吃饭,1放了筷子,2吃饭。方案是可行的,但是如果一个哲学家被阻塞在拿筷子步骤,mutex被锁,其他哲学家也别想拿筷子

重点:进程之间只存在互斥关系,每个进程需要同时持有多个临界资源,应该避免循环等待,防止死锁

管程

为什么引入管程
信号量机制编写程序困难,易出错
如何可以不关注复杂的PV操作

管程的组成

  1. 局部于管程的共享数据结构说明
  2. 对该数据结构进行操作的一组过程
  3. 对局部于管程的共性数据设置初始值的语句
  4. 管程有一个名字

管程的基本特征

  1. 局部于管程的数据只能被局部于管程的过程所访问
  2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
  3. 每次仅允许一个进程在管程内执行某个内部过程

将信号量的操作函数封装在管程里,进程通过调用管程的函数来实现功能。可以不用管互斥关系,只关注同步关系(我自己写的)

你可能感兴趣的:(生产者消费者、吸烟者、读者写者、哲学家进餐问题、管程)