有限缓冲区的生产者-消费者问题
有两组进程,一组是生产者进程P1,P2,…,Pn,另一组是消费者进程c1,c2,…,cm,它们共享一个公有的、固定大小的缓冲池,池中有n个缓冲区,一个缓冲区存放一个产品。生产者不断地制造出产品,并把它放入到指针in指定的缓冲区;而消费者不断地根据指针out指定的缓冲区把产品从缓冲区中取出来消费。要求这两组进程之间相互协调,使它们都能正确地完成自己的工作。
具有n个缓冲区的环型缓冲池,按顺时针生产和消费,in为生产者指针,生产完成一个产品,就将产品插入in指针指定的缓冲区,out为消费者指针,消费者从out指针指向的缓冲区中取出产品。
-
缓冲池满的条件:
(int + 1) mod n = out
-
缓冲池空的条件:
in = out
# 设置以下信号量:
# mutex,初值为1,用于互斥,控制互斥访问缓冲池。
# full,初值为0,表示当前缓冲池中已用缓冲区数,用于同步,即缓冲池中现有产品数,一个缓冲区放一个产品。
# empty,初值为n,表示当前缓冲池中空缓冲区数,用于同步。
void producer()
{
while(true){
produce; // 生产一个产品
P(empty); // 有无缓冲区
P(mutex); // 能否进入临界区
产品送往buffer(in); // 将产品放入in下标指定的缓冲区
in = (in - 1) mod n; // 生产者缓冲区下标+1
V(mutex); // 退出临界区
V(full); // 缓冲池中产品数+1
}
}
void consumer()
{
while(true){
P(full); // 有无产品消费
P(mutex); // 能否进入临界区
从buffer(out)中取出产品; // 从out下标指定的缓冲区取出产品
out = (out - 1) mod n; // 消费者缓冲区下标+1
V(mutex); // 退出临界区
V(empty); // 空缓冲区数+1
consume; // 消费取出的产品
}
}
void main()
{
semaphore empty=n, full=0, mutex=1;
cobegin
producer();
consumer();
coend
}
哲学家就餐问题
5位哲学家围坐在一张圆桌旁,每位哲学家面前都摆放着一盘面条,但面条非常滑,所以如果想要吃的话,必须同时使用两根筷子。在任意两个相邻的盘子之间都有一根筷子。每一位哲学家的动作只有两种:进餐和思考问题。当一位哲学家感到饥饿时,他会试图去获得位于他左手边和右手边的那两根筷子,每次只能取一根,且先后顺序无所谓,两根筷子都拿到后,才能开始进餐。在吃完以后,他需要把两根筷子放回原处,然后继续思考。5位哲学家和5根筷子都进行了编号,从0到4。依此类推,对于第i位哲学家,他的左手边是第i根筷子,右手边是第i+1根筷子。当然,最后一位哲学家除外,他左手边是4号筷子,而右手边是0号筷子。
解决方案1:每次最多只允许四个哲学家并发,同时拿筷子,这样肯定有一个哲学家能吃上面条,吃完后放回筷子,其他哲学家就能吃上面条。因此,可增设一个可同时就餐的人数信号量room,初值为4,表示每次可同时就餐人数最多4人。
# define N 5
void philosopher(int i)
{
do{
think();
P(room);
P(chopstick[i]);
P(chopstick[(i+1)%N]);
eat();
V(chopstick[(i+1)%N]);
V(chopstick[i]);
V(room);
}while(true);
}
void main()
{
semaphore chopstick[0...4] = {1, 1, 1, 1, 1};
semaphore room = 4; // 设置可同时就餐的人数信号量room,初值为4
int i;
cobegin
philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
coend
}
解决方案2:先判断哲学家的序号是奇数还是偶数,如是奇数,则先拿左边的筷子再拿右边的筷子;如果是偶数,则先拿右边的筷子再拿左边的筷子。
# define N 5
void philosopher(int i)
{
do{
think();
if(i mod 2 == 0)
{
P(chopstick[i]);
P(chopstick[(i+1)%N]);
eat();
V(chopstick[i]);
V(chopstick[(i+1)%N]);
}
else
{
P(chopstick[(i+1)%N]);
P(chopstick[i]);
eat();
V(chopstick[(i+1)%N]);
V(chopstick[i]);
}
}while(true);
}
读者-写者问题
在一个航空订票系统当中,有很多个竞争的进程想要访问(读或写)系统的数据集。访问的规则是:在任何时候,可以运行多个进程同时来读,但如果有一个进程想要修改该数据集,那么在此期间,其他任何进程都不能访问,包括读者和写者。从读者和写者对数据集的访问过程来看,在任何时候,“写者”最多只允许一个,而“读者”可以有多个。具体来说:“读-写”是互斥的、“写-写”是互斥的、“读-读”是允许的。
读者优先:只要有读进程陆续到来,它们一来就被允许进入,而写进程将一直被挂起直到没有一个读进程为止。
# 一个公共变量:rc,记录读进程的个数
# 两个信号量:mutex,保护对rc的访问初值为1、db控制写进程对数据集的访问,初值为1。
semaphore mutex = 1; // 保护对rc的访问
semaphore db = 1; // 控制对数据集的访问
int rc = 0; // 读数据集的读进程个数
void reader() // 读者进程
{
while(true)
{
P(mutex); // 互斥对rc的访问
rc = rc + 1; // 增加一个读进程数
if(rc == 1) // 当第一个读进程读数据集时,阻止写进程写。
P(db);
V(mutex); // 恢复对rc的访问
读数据集;
P(mutex); // 互斥对rc的访问
rc = rc - 1; // 读者减少一个
if(rc == 0) // 当最后一个读进程读完数据集时,允许写进程写
V(db);
V(mutex); // 恢复对rc的访问
}
}
void writer() // 写者进程
{
while(true)
{
P(db); // 排斥访问数据集
写数据库;
V(db); // 恢复访问数据集
}
}
void main()
{
cobegin // 两类进程并发执行
reader();
writer();
coend
}
写者优先:当有读进程正在读数据集时,有写进程请求访问,这时应禁止后续读进程的读请求,等到已在读数据集的读进程执行完毕则立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次投入运行。
semaphore mutex = 1; // 保护对rc的访问
semaphore db = 1; // 控制对数据集的访问
int rc = 0; // 读数据集的读进程个数
semaphore W = 1; // 提高写进程优先级
void reader() // 读者进程
{
while(true)
{
P(W); // 在无写进程请求时进入
P(mutex); // 互斥对rc的访问
rc = rc + 1; // 增加一个读进程数
if(rc == 1) // 当第一个读进程读数据集时,阻止写进程写。
P(db);
V(mutex); // 恢复对rc的访问
V(W); // 恢复对数据集的访问
读数据集;
P(mutex); // 互斥对rc的访问
rc = rc - 1; // 读者减少一个
if(rc == 0) // 当最后一个读进程读完数据集时,允许写进程写
V(db);
V(mutex); // 恢复对rc的访问
}
}
void writer() // 写者进程
{
while(true)
{
P(W); // 在无写进程请求时进入
P(db); // 排斥访问数据集
写数据库;
V(db); // 恢复访问数据集
V(W); // 恢复对数据集的访问
}
}
(最近更新:2019年09月18日)