1.生产者-消费者问题
有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将其所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品去消费。尽管所有的生产者进程和消费者进程都是以异步方式运行的,但它们之间必须保持同步,既不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区投放产品。
1.利用记录型信号量解决生产者-消费者问题
这时可以 利用互斥信号量mutex实现对诸进程的缓冲互斥作用,利用信号量empty和full分别表示缓冲池中的空缓冲区和满缓冲区的数量
代码:
int in = 0, out = 0;//in: 输入指针, out: 输出指针;
item buffer[n];//n个缓冲区组成的数组;
semaphore mutex = 1, full = 0, empty = n;
//mutex: 互斥信号量, 生产者进程和消费者进程都只能互斥访问缓冲区;
//full: 资源信号量, 满缓冲区的数量;
//empty: 资源信号量, 空缓冲区的数量;//信号量不允许直接参与运算, 故都要定义;
//生产者程序;
void Producer() {
do {
生产者生产一个产品nextp;
P(empty);//申请一个空缓冲区;
P(mutex);//申请临界资源;
buffer[in] = nextp;//将产品添加到缓冲区;
in = (in + 1) % n;//类似于循环队列;
V(mutex);//释放临界资源;
V(full);//释放一个满缓冲区;
} while (TRUE);
}
//消费者程序;
void Producer() {
do {
P(full);//申请一个满缓冲区;
P(mutex);//申请临界资源;
nextc = buffer[out];//将产品从缓冲区取出;
out = (out + 1) % n;//类似于循环队列;
V(mutex);//释放临界资源;
V(empty);//释放一个空缓冲区;
消费者将一个产品nextc消费;
} while (TRUE);
}
需要注意的是:
应先执行对资源信号量的申请,然后再对互斥信号量进行申请操作,否则会因起死锁。
汪先生来仔细分析一下造成死锁的原因,首先缓冲区有n个,每个都有满(full)和和空(empty)的两种状态,l临界区只有一个,控制权为murex,值为1,如果生产者首先拿到的是缓冲区的控制权,那么其他的生产者和消费者就拿不到这个控制权了对不对?对,接着这个生产者去申请一个空的缓冲区,如果此时这些个缓冲区刚好是满的,那么这个申请必然失败对不对,对,那么消费者还能消费吗?他拿不到临界区的控制权,无法消费,也就没有空的缓冲区腾出来,生产者申请空缓冲区也必然不成功,此时就是一个死锁的状态了。
2.利用AND型信号量解决生产者消费者问题
int in = 0, out = 0;//in: 输入指针, out: 输出指针;
item buffer[n];//n个缓冲区组成的数组;
semaphore mutex = 1, full = 0, empty = n;
//mutex: 互斥信号量, 生产者进程和消费者进程都只能互斥访问缓冲区;
//full: 资源信号量, 满缓冲区的数量;
//empty: 资源信号量, 空缓冲区的数量;//信号量不允许直接参与运算, 故都要定义;
//生产者程序;
void Producer() {
do {
Swait(empty,mutex);
buffer[in] = nextp;//将产品添加到缓冲区;
in = (in + 1) % n;//类似于循环队列;
Ssignal(mutex,full);
} while (TRUE);
}
//消费者程序;
void Producer() {
do {
Swait(full,mutex);
nextc = buffer[out];//将产品从缓冲区取出;
out = (out + 1) % n;//类似于循环队列;
Ssignal(mutex,empty);
消费者将一个产品nextc消费;
} while (TRUE);
}
2.哲学家进餐问题
一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭,如图2-10所示。哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿的时候,才试图拿起左、 右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
1.记录型信号量解决哲学家问题
分析:筷子就是临界资源,在一段时间内只允许一位哲学家使用,为了让实现筷子的互斥使用,可以一个筷子用一个信号量表示
semaphore chopstick[5]={1,1,1,1,1};
第i位哲学家的活动可以描述为:
do{
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);
//eat
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
//think
}while(TRUE);
分析,哲学家饿了会去先拿起它左边的筷子,然后拿起它右边的筷子,然后进餐,虽然可以保障不会有两个哲学家同时进餐,但是却有可能引起死锁。比如五个哲学家同时饥饿,同时拿起了左边的筷子,则大家都进入无限期的等待。
如何解决这个问题呢?这里说一种办法,其余可以参考博客:https://blog.csdn.net/thelostlamb/article/details/80741319
就是使用AND信号量机制,意思就是如果想给某个哲学家筷子,就将他需要的所有资源都给他,然后让他进餐,否则就一个都不给他。
2.利用AND信号量机制解决哲学家就餐问题
semaphore chopstick[5]={1,1,1,1,1};
do{
//think;
Swait(chopstick[(i+1)%5],chopstick[i]);
//eat
Ssignal(chopstick[(i+1)%5],chopstick[i]);
//think
}while(TRUE);
3.读-写
一个数据文件或记录文件可被多个进程共享,我们把只要求读该文件的进程称为“Reader进程”,其他进程则称为“Writer进程”。允许多个进程同时读一个共享对象,但是要读写互斥,或写写互斥。
1.利用记录型信号量解决读-写问题
为实现读写互斥,需要设置一个互斥信号量wmutex,设一个整型变量readcount表示正在读的进程数目。仅当Readcount=0,表示尚无Reader进程在读时,Reader进程才需要执行Wait(Wmutex)操作;仅当Reader进程执行了Readcount-1操作后其值为0时,才进行signal(Wmutex)操作。因为readcount是一个可以被多个进程访问的临界资源,所以还需为他设置一个互斥信号量rmutex..
semaphore rmutex=1,wmutex=1;
int readcount:=0;
void reader(){
do{
wait(rmutex);
if readcount=0 then wait(wmutex);
readcount++;
signal(rmutex);
...
perform read operation;
wait(rmutex);
readcount--;
if readcount=0 then signal(wmutex);
signal(rmutex);
}
}
void Writer(){
do{
wait(wmutex);
perform read operation;
signal(wmutex);
}
}
汪先生来分析一下这段代码,首先,这个读的进程是不需要拿到什么控制权的,因为多个进程读是不会产生安全问题的,但是要读写互斥就要顺带拿到写的控制权,并且要保证只要有一个进程在读,写进程就不可能拿到写的控制权。因为要读写互斥。然后会有很多的读进程来操作,都是没有问题的,当这些读的进程读完一个个退出的时候,最后退出的那个读进程必须要释放写的控制权。然后每个写的进程就必须要先拿到写临界区的控制权,才能写,写完再释放控制权。
4.最后一个例子
父亲向一个盘子中放一个苹果或桔子,儿子取桔子,女儿取苹果,盘子只能放一个水果
semaphore S=1,Sorange=1,Sapple=1;
void Father{
do{
wait(S);//申请空盘子
//放苹果
if(放的是苹果){
Signal(Sorange);
}
//放的是桔子
if(放的是桔子){
Signal(Sapple);
}
}
}
son(){
while(1){
P(So);
从盘中取出桔子;
V(S);
吃桔子;
}
}
daughter(){
while(1){
P(Sa);
从盘中取出苹果;
V(S);
吃苹果;
}
}