问题:
在两个城市南北方向之间存在一条铁路,多列火车可以分别从两个城市的车站
排队等待进入车道向对方城市行驶,该铁路在同一时间,只能允许在同一方向上行
车,如果同时有相向的火车行驶将会撞车。请模拟实现两个方向行车,而不会出现
撞车或长时间等待的情况。您能构造一个管程来解决这个问题吗?
解法:
结合代码来看,每一步都有注释:
关键代码部分是Wait(),Arrive(),Cross(),Quit();对于火车的进入和离开,分别建立两个锁,当某辆车(进程)进入时获取进入锁,执行完arrive释放进入锁,以供下一个车获取进入锁,如果获取进入锁的车不符合条件,那么wait(),等到车道上的车都离开了且发送signal信号,wait()的阻塞状态被解开,这辆车就能够进入车道,并释放进入锁。遵循FIFO原则,不会存在长时间等待现象。
离开锁就是同时间每次只能有一辆车离开车道。
(PS:注释里的中文是指导书自带的,由于在英文版ubuntu里编程,所以我写的注释都是英文的,看不懂的朋友可以直接用网易有道云词典翻译)
下面的代码都复制到一个文件夹里去,编写makefile之后在终端make就可以运行啦。
dp.h
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*信号灯控制用的共同体*/
typedef union semuns {
int val;
} Sem_uns;
//管程中使用的信号量
class Sema{
public:
Sema(int id);
~Sema();
int down(); //信号量加 1
int up(); //信号量减 1
private:
int sem_id; //信号量标识符
};
//管程中使用的锁
class Lock{
public:
Lock(Sema *lock);
~Lock();
void close_lock();
void open_lock();
private:
Sema *sema; //锁使用的信号量
};
//管程中使用的条件变量
class Condition{
public:
Condition(Sema *sema1, Sema *sema2);
~Condition();
void Wait(Lock *conditionLock,int direct); //条件变量阻塞操作
int Signal(int direc); //条件变量唤醒操作
private:
Sema *sema0; //a block queue of one way
Sema *sema1; //the other
Lock *lock; //the lock acquired when entering the tube;
};
class OneWay{
public:
OneWay (int maxall,int maxcur);
~OneWay();
void Arrive(int direc);//car is about to ride on oneway
void Cross(int direc);//on the oneway
void Quit(int direc);//pass the oneway
int *eastCount;//tot
int *westCount;//tot
int *eastWait;
int *westWait;
int *sumPassedCars;//total amount of already passed cars;
private:
//build functions to get ipc sema;
int get_ipc_id(char *proc_file,key_t key);
int set_sem(key_t sem_key,int sem_val,int sem_flag);
//create sharing memory
char *set_shm(key_t shm_key,int shm_num,int shm_flag);
int rate;//velocity of the car;
int *maxCars;//max amount of cars in the same direction
int *numCars;//amount of cars which is passing;
int *currentDire;// current crossing car's direction
Condition *condition;// condition variable to cross the oneway
Lock *lock1,*lock2,*lock3;
};
Dp.cpp
#include "dp.h"
using namespace std;
#define BUFSZ 256
Sema::Sema(int id)
{
sem_id = id;
}
Sema::~Sema(){ }
/*
* 信号灯上的 down/up 操作
* semid:信号灯数组标识符
* semnum:信号灯数组下标
* buf:操作信号灯的结构
*/
int Sema::down()
{
struct sembuf buf;
buf.sem_op = -1;
buf.sem_num = 0;
buf.sem_flg = SEM_UNDO;
if((semop(sem_id,&buf,1)) <0) {
perror("down error ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
int Sema::up()
{
Sem_uns arg;
struct sembuf buf;
buf.sem_op = 1;
buf.sem_num = 0;
buf.sem_flg = SEM_UNDO;
if((semop(sem_id,&buf,1)) <0) {
perror("up error ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
/*
* 用于oneway管程的互斥执行
*/
Lock::Lock(Sema * s)
{
sema = s;
}
Lock::~Lock(){ }
//上锁
void Lock::close_lock()
{
sema->down();
}
//开锁
void Lock::open_lock()
{
sema->up();
}
int OneWay::get_ipc_id(char *proc_file,key_t key){
FILE *pf;
int i,j;
char line[BUFSZ],column[BUFSZ];
if((pf=fopen(proc_file,"r"))==NULL){
perror("proc file not open");
exit(EXIT_FAILURE);
}
fgets(line,BUFSZ,pf);
while(!feof(pf)){
i=j=0;
fgets(line,BUFSZ,pf);
while(line[i]==' ')
i++;
while(line[i]!=' '){
column[j++]=line[i++];
}
column[j]='\0';
if(atoi(column)!=key)
continue;
j=0;
while(line[i]==' ')
i++;
while(line[i]!=' '){
column[j++]=line[i++];
}
column[j]='\0';
i=atoi(column);
fclose(pf);
return i;
}
fclose(pf);
return -1;
}
/*
*
set_sem 函数建立一个具有 n 个信号灯的信号量
*
如果建立成功,返回 一个信号量的标识符 sem_id
*
输入参数:
*
sem_key 信号量的键值
*
sem_val 信号量中信号灯的个数
*
sem_flag 信号量的存取权限
*/
int OneWay::set_sem(key_t sem_key,int sem_val,int sem_flg)
{
int sem_id;
Sem_uns sem_arg;
//测试由 sem_key 标识的信号量是否已经建立
if((sem_id=get_ipc_id("/proc/sysvipc/sem",sem_key)) < 0 ){
//semget 新建一个信号灯,其标号返回到 sem_id
if((sem_id = semget(sem_key,1,sem_flg)) < 0){
perror("semaphore create error");
exit(EXIT_FAILURE);
}
}
//设置信号量的初值
sem_arg.val = sem_val;
if(semctl(sem_id,0,SETVAL,sem_arg) < 0){
perror("semaphore set error");
exit(EXIT_FAILURE);
}
return sem_id;
}
/*
*
set_shm 函数建立一个具有 n 个字节 的共享内存区
*
如果建立成功,返回 一个指向该内存区首地址的指针 shm_buf
*
输入参数:
*
shm_key 共享内存的键值
*
shm_val 共享内存字节的长度
*
shm_flag 共享内存的存取权限
*/
char * OneWay::set_shm(key_t shm_key,int shm_num,int shm_flg)
{
int i,shm_id;
char *shm_buf;
//测试由 shm_key 标识的共享内存区是否已经建立
if((shm_id=get_ipc_id("/proc/sysvipc/shm",shm_key))<0){
//shmget 新建 一个长度为 shm_num 字节的共享内存
if((shm_id= shmget(shm_key,shm_num,shm_flg)) <0){
perror("shareMemory set error");
exit(EXIT_FAILURE);
}
//shmat 将由 shm_id 标识的共享内存附加给指针 shm_buf
if((shm_buf=(char *)shmat(shm_id,0,0)) < (char *)0){
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
for(i=0; i<shm_num; i++) shm_buf[i] = 0; //初始为 0
}
//共享内存区已经建立,将由 shm_id 标识的共享内存附加给指针 shm_buf
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0) {
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
return shm_buf;
}
Condition::Condition(Sema *semax1,Sema *semax2){
sema0=semax1;
sema1=semax2;
}
/*
* check if it can pass
*/
void Condition::Wait(Lock *lock,int direc){
if(direc == 0){
// cout<
//lock->open_lock();
sema0->down();
//lock->close_lock();
}
else if(direc==1){
// cout<
// lock->open_lock();
sema1->down();
// lock->close_lock();
}
}
int Condition::Signal(int direc){
int i;
if(direc==0)//wake up a direction
{
i=sema0->up();
}
else if(direc==1){
i=sema1->up();
}
return i;
}
Condition::~Condition(){}
OneWay::OneWay(int maxall,int maxcur){
Sema *sema0;
Sema *sema1;
Sema *semaLock1,*semaLock2,*semaLock3;
int ipc_flg=IPC_CREAT|0644;
maxCars=(int *) set_shm(100,1,ipc_flg);
numCars=(int *) set_shm(200,1,ipc_flg);
currentDire=(int *) set_shm(300,1,ipc_flg);
eastCount=(int *) set_shm(501,1,ipc_flg);
westCount=(int *) set_shm(502,1,ipc_flg);
sumPassedCars=(int *) set_shm(700,1,ipc_flg);
eastWait=(int *) set_shm(801,1,ipc_flg);
westWait=(int *) set_shm(802,1,ipc_flg);
int sema0_id=set_sem(401,0,ipc_fl/g);
int sema1_id=set_sem(402,0,ipc_flg);
int semaLock1_id=set_sem(601,maxcur,ipc_flg);
int semaLock2_id=set_sem(602,maxcur,ipc_flg);
int semaLock3_id=set_sem(603,maxcur,ipc_flg);
//init
*maxCars=maxcur;
*numCars=0;
*currentDire=0;
*eastCount=0;
*westCount=0;
*sumPassedCars=0;
*eastWait=0;
*westWait=0;
sema0=new Sema(sema0_id);
sema1=new Sema(sema1_id);
semaLock1=new Sema(semaLock1_id);
semaLock2=new Sema(semaLock2_id);
semaLock3=new Sema(semaLock3_id);
lock1=new Lock(semaLock1);
lock2=new Lock(semaLock2);
lock3=new Lock(semaLock3);
condition=new Condition(sema0,sema1);
}
void OneWay::Arrive(int direc){
lock1->close_lock();//FIFO,if lock1 is already taken by a different direction,then stop and wait
if((*currentDire!=direc||*numCars>=*maxCars)&*sumPassedCars>0)
{
if(direc==0){
*eastWait+=1;
}
else if(direc==1){
*westWait+=1;
}
condition->Wait(lock1,direc);
if(direc==0){
*eastWait-=1;
}
else if(direc==1){
*westWait-=1;
}
}// cout<<"ddddd"<
*currentDire=direc;
*numCars=*numCars+1;
*sumPassedCars+=1;
lock1->open_lock();
if(direc==0){
//*eastWait-=1;//这个地方出bug了
*eastCount=*eastCount+1;
cout<<getpid()<<"car ride into oneway,direct to east"<<endl;
}
else if(direc==1){
//*westWait-=1;
*westCount=*westCount+1;
cout<<getpid()<<"car ride into oneway,direct to west"<<endl;
}
}
void OneWay::Cross(int direc){
//lock2->close_lock();
if(direc==0){
cout<<getpid()<<"car pass through oneway,direction to east,car amount on oneway:"
<<*numCars<<endl;
}
else if(direc==1){
cout<<getpid()<<"car pass through oneway,direction to west,car amount on oneway:"
<<*numCars<<endl;
}
sleep(4);
//lock2->open_lock();
}
void OneWay::Quit(int direc){
lock3->close_lock();
*numCars-=1;
if(direc==0){
cout<<getpid()<<"car leaves oneway,direction to east"<<endl;
}else if(direc==1){
cout<<getpid()<<"car leaves oneway,direction to west"<<endl;
}
//cout<<*numCars<
if(*numCars==0){
if(direc==0){
if(*westWait>0){
condition->Signal(1);
}
else if(*eastWait>0){
condition->Signal(0);
}
}
else if(direc==1){
if(*eastWait>0){
condition->Signal(0);
}
else if(*westWait>0){
condition->Signal(1);
}
}
}
lock3->open_lock();
}
OneWay::~OneWay(){
delete condition;
}
int main(int argc,char **argv){
int maxCars;
int maxSingleDirect;
cout<<"Please enter total amount of cars:";
cin>>maxCars;
cout<<"Please enter the max amount of cars in the same direction:";
cin>>maxSingleDirect;
OneWay *oneway=new OneWay(maxCars,maxSingleDirect);
//build tube, check if enterable,decide the direction to ride into oneway;
int i;
int pid[maxCars];
srand(time(NULL));
for(i=0;i<maxCars;i++){
pid[i]=fork();
//when a new process is running,the random series will reset,
//so we should decide the direction outside the declaration if();
int direct;
direct=rand()%2;//fair decision of direction
if(pid[i]==0){
sleep(1);
//direct=*oneway->sumPassedCars%2;
oneway->Arrive(direct);
oneway->Cross(direct);
oneway->Quit(direct);
exit(EXIT_SUCCESS);
}
}
for(int i=0;i<maxCars;i++){
waitpid(pid[i],NULL,0);
}
cout<<*(oneway->eastCount)<<" cars to east,"<<*(oneway->westCount)
<<" cars to west, transportation is running regularly."<<endl;
delete oneway;
return EXIT_SUCCESS;
}
makefile:
head=dp.h
srcs=Dp.cpp
objs=Dp.o
opts=-w -g -c
all: Dp
Dp: $(objs)
g++ $(objs) -o Dp
Dp.o: $(head) $(srcs)
g++ $(opts) $(srcs)
clean:
rm Dp *.o