主要从生产者消费者、读写者、哲学家问题中的经典变体进行讲述,均使用伪代码实现
生产者消费者变体
顾客看作是生产出的产品,理发师看作是消费者,沙发有空位,顾客就进去,沙发有顾客,理发师就去理发
和生产者消费者的区别在于
- 生产者生产出一个产品后,当缓存没有空位时就会一直等待下去,直到有空位的出现
- 此问题如果没有空位,用户直接离开,不会加入阻塞队列
semaphore customers = 0,barber = 0;//沙发上的顾客数和正在理发的理发师数目
semaphore mutex = 1;
int count = 0;//沙发上的顾客人数,用来技术
void barber() {
do {
wait(customer);//如果沙发有顾客,就起来理发,否则阻塞理发师睡觉
wait(mutex);//保护count
count = count - 1;//等待的顾客数减一,开始剪头
signal(babers); //通知理发师可以开始理发
signal(mutex);
haircut();
}while(TRUE);
}
//没有空位顾客直接离开,所以不需要while循环
void customer() {
wait(mutex);
if (count < n) {
//如果有空椅子则等待的顾客数+1
count = count + 1;
signal(customers);
signal(mutex);
wait(barbers);//如果理发师忙则等待
}else {
signal(mutex);//无椅子离开
}
}
单缓冲区生产者消费者
有一计算进程和打印进程,它们共享一个单缓冲区,计算进程不断的计算出一个整形结果并将它放入缓冲区,打印进程则负责从单缓冲区中取出每一个结果进行打印
生产者消费者-单缓冲区
//生产者消费者的完整复杂写法
semaphore mutex = 1;
semaphore full = 0,empty = 1;
int buffer[1];
int in = 0,out = 0;
void calculator() {
int nextp;
do {
calculate into nextp;
wait(empty);
wait(mutex);
buffer[in] = nextp;
in = (in + 1) % 2;
signal(mutex);
signal(full);
}while(TRUE);
}
void printer() {
int nextc;
do {
wait(full);
wait(mutex);
nextc = buffer[out];
out = (out + 1) % 2;
signal(mutex);
signal(full);
print(nextc);
}while(TRUE);
}
对于单缓冲区而言,可以不需要mutex、buffer数组和in out指针
//生产者消费者的完整复杂写法
semaphore full = 0,empty = 1;
int buffer;
void calculator() {
int nextp;
do {
calculate into nextp;
wait(empty);
buffer = nextp;
signal(full);
}while(TRUE);
}
void printer() {
int nextc;
do {
wait(full);
nextc = buffer;
signal(full);
print(nextc);
}while(TRUE);
}
多缓冲区生产者消费者变体
有三个进程PA、PB、PC协作解决文件打印问题,PA将文件记录从磁盘读入内存的缓冲区1,每执行一次读入一个记录;PB将缓冲区1的内容复制到缓冲区2中,每执行一次复制一条记录;PC将缓冲区2的内容打印出来,每执行一次打印一条;缓冲区的大小和记录大小一样,请用信号量来保证文件的正确打印。
分析:
该问题中PA是缓冲区1的生产者,PB是缓冲区1的消费者、缓冲区2的生产者,PC是缓冲区2的消费者
该问题为多缓冲区多生产者消费者的变体
因为缓冲区大小为1,故不需要互斥访问量
semaphore full1 = 0,full2 = 0;
semaphore empty1 = 1,empty2 = 1;
item buff1;
item buff2;
void PA() {
item read1;
do {
//从磁盘读入read1;
wait(empty1);
buff1 = read1;
signal(full1);
}while(true);
}
void PB() {
item tmp;
do {
wait(full1);
tmp = buff1;//取出缓冲区1的数据
signal(empty1);
wait(empty2);
buff2 = tmp;//存入缓冲区2
signal(full2);
}while(true);
}
void PC() {
item print;
do {
wait(full2);
print = buff2;//取出数据
signal(empty2);
print(print);//打印
}
}
生产者消费者变形
进程A1、A2、…An1通过m个缓冲区向进程B1、B2…Bn2不断地发送消息。发送和接收工作遵循如下规则:
- 每个发送进程一次发送一个消息,写入一个缓冲区,缓冲区大小与消息长度相同
- 对每个消息,B1、…Bn2都需各自接收一次,读入自己的数据区内
- m个缓冲区都满的时候,发送进程等待,没有可读的信息时,接收进程等待
提取信息:
发送进程只发送一次,接收进程取n2次,假设缓冲区长度为n2
设置信号量
mutex=1用来实现对缓冲区的互斥访问
empty[i] (i=1,…n2),初值为m,每个empty[i]对应缓冲池第i格中的所有空闲缓冲区
full[i] (i=1…n2) 初值为0,对应缓冲区第i格中装有消息的缓冲区。
提供整形变量in指示将消息放入哪个缓冲区
out[j] (j=1…n2)用来指示Bj从哪个缓冲区中取消息
Ai() {//i = 1...n1
int k;
while (1) {
for (k = 1;k <= n2;k++) wait(empty[k]);//申请大小为n2的空闲分区
wait(mutex);
把Ai的消息放入第in个缓冲区中
in = (in + 1) % m;
signal(mutex);
for (k = 1;k <= n2;k++) signal(full[k]);
}
}
Bi() {
while (1) {
wait(full[j]);
wait(mutex);
从第out[j]个缓冲区的第j格中取出消息
out[j] = (out[j] + 1) % m;
signal(mutex);
signal(empty[j]);
把数据写到消息区
}
}
生产多个产品的生产者
桌上有个能盛的下五个水果的空盘子,爸爸不停向盘中放入苹果或橘子,儿子不停的从盘中取出橘子,女儿不停的从盘中取出苹果。规定三人不能同时往盘中取放水果,用信号量实现三个循环进程之间的同步
分析:
典型的生产者消费者问题,爸爸为能生产两个产品的生产者,缓冲区大小为5,需要使用mutex来互斥访问
semaphore mutex = 1,orange = 0,apple = 0,empty = 5;
void Father() {
while (1) {
wait(empty);
wait(mutex);
//将水果放入盘中
signal(mutex);
if (放的橘子) {
signal(orange);
}else{
signal(apple);
}
}
}
void Son() {
while (1) {
wait(orange);
wait(mutex);
取出橘子
signal(mutex);
signal(empty);
吃了
}
}
void Daughter() {
while (1) {
wait(apple);
wait(mutex);
取出苹果
signal(mutex);
signal(empty);
吃了
}
}
多个生产者+限制
设有两个生产者进程A、B和一个销售者进程C,它们共享一个无限大的仓库,生产者每次循环生成一个产品,然后入库供销售者销售;销售者每次循环从仓库中取出一个产品进行销售。如果不允许同时入库,也不允许边入库边出库;而且要求生产A产品和B产品的件数满足以下的关系:-n<=A的件数-B的件数<=m
,其中n,m为正整数,但是对仓库中的A、B无限制,请用信号量机制写出A、B、C的工作流程
分析:
缓冲池:无限大的仓库,临界资源,使用mutex互斥
AB差值的约束,若A生产1份差值加一,B生产一份差值减一,则用信号量来控制这个差值:
SAB【sub A B】= m【允许A生产的产品数量】,SBA【sub B A】 = n【允许B生产的产品数量】
仓库无限大,但是C的销售需要仓库中的产品数量参考,设信号量S = 0代表仓库中初始产品个数
semaphore mutex = 1,SAB = m,SBA = n,S = 0;
A () {
while (1) {
//生产产品
wait(SAB);//生产一个A
wait(mutex);
signal(SBA);//B能生产的个数+1
//放入仓库
signal(S);
signal(mutex);
}
}
B () {
while (1) {
//生产产品
wait(SBA);
wait(mutex);
signal(SAB);
//放入仓库
signal(S);
signal(mutex);
}
}
C() {
while (1) {
wait(S);
wait(mutex);
//取出仓库产品
signal(mutex);
//销售
}
}
读写者变体
请出一种写者优先的读者写者问题的算法描述
在读者优先的算法上,增添一个初值为1的信号量S,使得当至少一个写者准备访问共享对象时,它可使后续的读者进程等待写完成;初值为0的writecount对写者进行计数;初值为1的互斥信号量wmutex,实现多个写着对writecount的互斥访问
reader() {
while (1) {
wait(S);
wait(rmutex);
if (readcount == 0) wait(mutex);
readcount++;
signal(rmutex);
signal(S);
//读
wait(rmutex);
readcount--;
if (readcount == 0) signal(mutex);
signal(rmutex);
}
}
writer() {
while (1) {
wait(wmutex);
if (writecount == 0) wait(rmutex);
writecount++;
signal(wmutex);
wait(mutex);
//写
signal(mutex);
wait(wmutex);
writecount--;
if (writecount == 0) signal(S);
signal(wmutex);
}
}
请用信号量解决以下的过独木桥问题:同一方向的行人可连续过桥,当某一方向有人过桥时,另一方向的行人必须等待,当某一方向无人过桥时,另一方向行人可以过桥
独木桥问题是读者写者问题的变形,相当于两种不同的读者,相同读者可以同时读,不同读者必须互斥
semaphore bridge = 1;//用来实现不同方向行人对桥的互斥
int countA,countB = 0;//AB两个方向过桥的人数
semaphore mutexA,mutexB = 1;//用来实现对countA、countB的互斥共享
void PA() {
wait(mutexA);
if (countA == 0) wait(bridge);
countA++;
signal(mutexA);
过桥
wait(mutexA);
countA--;
if (countA == 0) signal(bridge);
signal(mutexA);
}
//B的代码和A一致,都是读者格式代码
哲学家变体
有一间酒吧里有3个音乐爱好者队列,第一队的音乐爱好者只有随身听,第二队的音乐爱好者只有磁带,第三队的只有电池,而要听音乐必须三个俱全。酒吧老板一次出售三种中的任意两种。当一名音乐爱好者得到三种并听完一首乐曲后,98老板才能再次出手三种的两种,一直循环进行下去。
三种临界资源随身听、磁带、电池。
记磁带电池为S1,随身听电池S2,随身听磁带S3【两种物品必须被同一个音乐爱好者取走,将两个看作一个组合起来的临界资源】
一次最多只有一个完整的组合,听完后才能再次出售商品,记为music
semaphore S1 = S2 = S3 = 1,music = 0;
void fan1() {
wait(S1);
//购买磁带电池
//听音乐
signal(music)
}
void fan2() {
wait(S2);
//购买随身听电池
//听音乐
signal(music);
}
//fan3同理
void boss() {
while (1) {
//提供任意两种产品售卖
if (卖的是磁带电池) {
signal(S1);
}else if (卖的是随身听电池){
signal(S2);
}else{
Signal(S3);
}
wait(music);
}
}