作者:潘家邦
2012年12月6日
面包师问题是进程间通信的经典问题。本文就面包师问题进行讨论,并在linux上编程实现。平台说明:Linux Mint 14,G++ 4.7.2。
面包师有很多面包和蛋糕,由n个销售人员销售。每个顾客进店后先取一个号,并且等着叫号。当一个销售人员空闲下来,就叫下一个号。请分别编写销售人员和顾客进程的程序。
使用信号量解决该问题。首先分析客户和销售人员之间的关系。从问题描述中我们可以看出,客户是排队等待的,而且只有一条队列。当新出现一个客户,则等待队列的长度加一。当销售人员叫号,则等待队列长度减一。客户排队从另一种角度来看,可以认为客户进程被阻塞了,在PV操作中,V操作是不会导致阻塞的,所以把队列长度当作信号量是不会导致客户进程被阻塞的。注意到一共有n个销售人员,当一个销售人员空闲下来,就叫下一个号。换个角度看,销售人员最大空闲人数为n,每出现一个客户,则空闲人数减一,当空闲人数为0时,新出现的客户被阻塞,直到空闲人数大于0,阻塞的客户才被唤醒。我们还发现,不止客户在排队,销售人员在排队的情况也会发生。假设客户的等待队列长度为0,即没有客户在排队,那么这时销售人员应该被阻塞,直到新来一个客户,将销售人员唤醒。于是队列长度成为第二个信号量。
模仿《现代操作系统(第二版)》的格式,下面分别给出销售人员和客户的伪代码。
typedef int Semaphore;
Semaphore num_of_free = 0;
Semaphore num_of_waiting = 0;
void customer()
{
get_code();
up(&num_of_waiting);
down(&num_of_free);
buy_cake();
up(&num_of_free);
}
void salesman()
{
up(&num_of_free);
while(true)
{
down(&num_of_waiting);
sell_cake();
}
}
接下来将解释这段伪代码的逻辑。每启动一个salesman, 则空闲的salesman数目增加。在某一时刻,没有customer在等待,num_of_waiting为0,则salesman在执行到循环体的down语句将被阻塞。如果来了一个customer,则customer的第一个up语句将有可能唤醒某个处于阻塞态的salesman,然后执行down语句。如果没有salesman处于“空闲”,即num_of_free为0,则customer在down语句内被阻塞,直到某个salesman被启动或者某个customer完成了buy_cake并发起up操作来唤醒其他被阻塞在num_of_free上的customer。于是这就保证了num_of_free这个信号量不会大于n。
使用Linux的System V风格信号量,主要可执行文件为cumtomer、salesman,分别是客户和销售人员的模拟程序。另外还有可执行文件state,用于输出当前信号量的值;可执行文件state_clear,用于将信号量置零。Python脚本coming.py,用于模拟客户到来的情况。所有可执行文件需要在root权限下执行。文件列表:coming.py makefile semaphore.cpp state_clear.cpp customer.cpp salesman.cpp semaphore.h state.cpp
我对linux的信号量的操作做了一些封装,隐藏其复杂性,使之在本问题的主要代码中看起来简洁。这是封装之后的接口。
#ifndef SEMAPHORE_H
#define SEMAPHORE_H
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
int semaphore_create();
int output_semaphore(int sid);
int up_num_of_waiting(int sid);
int down_num_of_waiting(int sid);
int up_num_of_free(int sid);
int down_num_of_free(int sid);
#endif
这是semaphore接口的实现。
#include "semaphore.h"
#include <stdio.h>
int semaphore_create()
{
int key = ftok(".", 1);
return semget(key, 2, IPC_CREAT);
}
int output_semaphore(int sid)
{
printf("num of waiting: %d\n", semctl(sid, 0, GETVAL));
printf("num of free : %d\n", semctl(sid, 1, GETVAL));
}
int up_num_of_waiting(int sid)
{
sembuf operation = {0, 1, SEM_UNDO};
return semop(sid, &operation, 1);
}
int down_num_of_waiting(int sid)
{
sembuf operation = {0, -1, SEM_UNDO};
return semop(sid, &operation, 1);
}
int up_num_of_free(int sid)
{
sembuf operation = {1, 1, SEM_UNDO};
return semop(sid, &operation, 1);
}
int down_num_of_free(int sid)
{
sembuf operation = {1, -1, SEM_UNDO};
return semop(sid, &operation, 1);
}
这是客户程序的实现。
#include <unistd.h>
#include <iostream>
#include "semaphore.h"
using namespace std;
int main()
{
int pid = getpid();
cout << "a customer come, get pid " << pid << endl;
int sid = semaphore_create();
up_num_of_waiting(sid);
down_num_of_free(sid);
cout << "a salesman is free, pid "<< pid << " go for her" << endl;
up_num_of_free(sid);
return 0;
}
这是销售人员的的实现。
#include <iostream>
#include "semaphore.h"
using namespace std;
int main()
{
int sid = semaphore_create();
up_num_of_free(sid);
while(true)
{
down_num_of_waiting(sid);
cout << "a customer is waiting, I call him" << endl;
}
return 0;
}
可以方便的查看信号量的值。
#include "semaphore.h"
int main()
{
int sid = semaphore_create();
output_semaphore(sid);
return 0;
}
将所有信号量置零。方便重复调试。
#include "semaphore.h"
int main()
{
int sid = semaphore_create();
semun sem;
sem.val = 0;
semctl(sid, 0, SETVAL, sem);
semctl(sid, 1, SETVAL, sem);
return 0;
}
自动化编译脚本。
baker_problem: customer salesman state state_clear
.PHONY : baker_problem
customer: customer.o semaphore.o
g++ -o customer customer.o semaphore.o
salesman: salesman.o semaphore.o
g++ -o salesman salesman.o semaphore.o
state:state.o semaphore.o
g++ -o state state.o semaphore.o
state_clear: state_clear.o semaphore.o
g++ -o state_clear state_clear.o semaphore.o
semaphore.o:
customer.o:
salesman.o:
state.o:
state_clear.o:
clean:
rm *.o customer salesman state state_clear
使用python按照一定的时间间隔启动cuntomer。
#!/usr/bin/python
import os
while True:
os.system('sudo ./customer')
os.system('sleep 5')