实验要求 在两个城市南北方向之间存在一条铁路,多列火车可以分别从两个城市的车站 排队等待进入车道向对方城市行驶,该铁路在同一时间,只能允许在同一方向上行 车,如果同时有相向的火车行驶将会撞车。请模拟实现两个方向行车,而不会出现 撞车或长时间等待的情况。您能构造一个管程来解决这个问题吗? 参考解法分析: 管程-Monitor 管程是一种高级抽象数据类型,它支持在它的函数中隐含互斥操作。结合条件 变量和其他一些低级通信原语,管程可以解决许多仅用低级原语不能解决的同步问 题。 利用管程可以提供一个不会发生死锁或饥饿现象的对象;哲学家就餐问题和 Java 语言中的 synchronized 对象都是很好的管程的例子. 管程封装了并发进程或线程要互斥执行的函数。 为了让这些并发进程或线程在管 程内互斥的执行,进入管程的进/线程必须获取到管程锁或二值信号量 条件变量 Condition Variables 条件变量提供了一种对管程内并发协作进程的同步机制。如果没有条件变量, 管程就不会有很有用。多数同步问题要求在管程中说明条件变量。条件变量代表了 管程中一些并发进程或线程可能要等待的条件。一个条件变量管理着管程内的一个 等待队列。如果管程内某个进程或线程发现其执行条件为假,则该进程或线程就会 被条件变量挂入管程内等待该条件的队列。如果管程内另外的进程或线程满足了这 个条件,则它会通过条件变量再次唤醒等待该条件的进程或线程,从而避免了死锁 的产生。所以,一个条件变量 C 应具有两种操作 C.wait()和 C.signal()。 当管程内同时出现唤醒者和被唤醒者时,由于要求管程内的进程或线程必须互 斥执行,因此就出现了两种样式的条件变量: Mesa Style(signal-and-continue): 唤醒者进程或线程继续执行,被唤醒 者进程或线程等到唤醒者进程或线程阻塞或离开管程后再执行。 Hoare Style(signal-and-wait): 被唤醒者进程或线程立即执行,唤醒者进 程或线程阻塞,直道被唤醒者阻塞或离开管程后再执行。 实验 6 单行道(过桥)问题可以通过管程很好的解决。 可以把单行道/桥封装为一 个管程类,桥上通过的车辆是进入管程的进/线程,可以通过创建多个车辆进/线程 并随机产生它们的行进方向,并指定桥上可同时行驶的车辆的个数来模拟该问题的 各种现场随机情况。一个正确的实验结果应能实现在各种随机现场情况下车辆进程 不会逆向上桥(死锁),也不会使车少方向上的车辆无机会上桥(饥饿).
伪代码: 参考解法伪代码: 以下是一个单行道管程类及其方法和属性的大致描述: 定义一个单行道管程类: class OneWay { public: OneWay(); ~OneWay(); void Arrive(int direc); // 车辆准备上单行道,direc 为行车方向 void Cross(int direc); // 车辆正在单行道上 void Quit(int direc); // 车辆通过了单行道 private: int rate; //车速 int *maxCars; //最大同向车数 int *numCars; //当前正在通过的车辆数 int *currentDirec; //当前通过的车辆的方向 Condition *OneWayFull; //通过单行道的条件变量 Lock *lock; //单行道管程锁 }; 定义一个进入管程的信号量 Sema 类和锁 Lock 类(可参考实验六的示例程序). 定义一个单行道管程的条件变量类: class Condition { public: Condition(Sema *sema1 , Sema *sema2); ~Condition(); void Wait(Lock *conditionLock,int direc); //过路条件不足时阻塞 void Signal( int direc); //唤醒相反方向阻塞车辆 private: Sema* queue1; // 一个方向阻塞队列 Sema* queue2; // 另一方向阻塞队列 Lock* lock; // 进入管程时获取的锁 }; Mesa 型条件变量的 Wait 和 Signal 方法:(也可设计成 Haoro 样式) Condition::Wait( Lock *conditionLock,int direct) { 保存当前条件锁; 释放锁; 进入所在方向等待队列睡眠; 被唤醒后重新获取锁; } Condition::Signal( Lock *conditionLock) { 唤醒相反方向队列等待者 } 单行道管程的 Arrive 和 Quit 方法: OneWay::Arrive(int direc) { 获取管程锁; 如果当前方向不是我的方向或者单行道上有车且车辆数大于等于上限数 在条件变量上等待; 单行道上车辆数加 1; 当前方向为我的方向; 释放管程锁; } OneWay::Quit(int direc) { 获取管程锁; 单行道上车辆数减 1; 车辆数为 0 唤醒相反方向队列中的等待者 释放管程锁 } 主程序 main() { 建立单行道管程; 建立多个行车子进程(最好不少于 5 个),每个随机设定一个行车方向; 每个子进程作: while(1){ 单行道管程->Arrive(direc); 单行道管程->Cross(direc); 单行道管程->Exit(direc); } }
程序执行结果的测试和分析: 1、单行道最多允许一辆车行驶,不同方向同时到达五辆车$ ./oneway 5 1 3429 号车向东进入单行道 3429 号车向东通过单行道,道上车数:1 3430 号车向东等待单行道 3431 号车向西等待单行道 3428 号车向西等待单行道 3427 号车向西等待单行道 3429 号车向东离开单行道 3431 号车向西进入单行道 3431 号车向西通过单行道,道上车数:1 3428 号车向西等待单行道 3427 号车向西等待单行道 3431 号车向西离开单行道 3430 号车向东进入单行道 3430 号车向东通过单行道,道上车数:1 3428 号车向西等待单行道 3430 号车向东离开单行道 3427 号车向西进入单行道 3427 号车向西通过单行道,道上车数:1 3428 号车向西等待单行道 3427 号车向西离开单行道 3428 号车向西进入单行道 3428 号车向西通过单行道,道上车数:1 3428 号车向西离开单行道 两辆向东三辆向西,正常通过。 2、单行道最多允许两辆车同向行驶,不同方向同时到达七辆车。 ./oneway 7 2 3520 号车向东进入单行道 3520 号车向东通过单行道,道上车数:1 3522 号车向东进入单行道 3522 号车向东通过单行道,道上车数:2 3523 号车向东等待单行道 3521 号车向东等待单行道 3524 号车向西等待单行道 3525 号车向西等待单行道 3526 号车向东等待单行道 3520 号车向东离开单行道 3522 号车向东离开单行道 3524 号车向西进入单行道 3524 号车向西通过单行道,道上车数:1 3525 号车向西进入单行道 3525 号车向西通过单行道,道上车数:2 3524 号车向西离开单行道 3525 号车向西离开单行道 3523 号车向东进入单行道 3523 号车向东通过单行道,道上车数:1 3521 号车向东进入单行道 3521 号车向东通过单行道,道上车数:2 3526 号车向东等待单行道 3523 号车向东离开单行道 3521 号车向东离开单行道 3526 号车向东进入单行道 3526 号车向东通过单行道,道上车数:1 3526 号车向东离开单行道 五辆向东两辆向西,正常通过。 3、单行道最多允许四辆车同向行驶,不同方向同时到达十辆车。 $ ./oneway 10 4 3704 号车向西进入单行道 3704 号车向西通过单行道,道上车数:1 3706 号车向东等待单行道 3708 号车向东等待单行道 3705 号车向西进入单行道 3705 号车向西通过单行道,道上车数:2 3707 号车向西进入单行道 3707 号车向西通过单行道,道上车数:3 3709 号车向西进入单行道 3709 号车向西通过单行道,道上车数:4 3710 号车向东等待单行道 3711 号车向东等待单行道 3712 号车向东等待单行道 3713 号车向东等待单行道 3704 号车向西离开单行道 3705 号车向西离开单行道 3707 号车向西离开单行道 3709 号车向西离开单行道 3706 号车向东进入单行道 3706 号车向东通过单行道,道上车数:1 3708 号车向东进入单行道 3708 号车向东通过单行道,道上车数:2 3710 号车向东进入单行道 3710 号车向东通过单行道,道上车数:3 3711 号车向东进入单行道 3711 号车向东通过单行道,道上车数:4 3712 号车向东等待单行道 3713 号车向东等待单行道 3706 号车向东离开单行道 3708 号车向东离开单行道 3710 号车向东离开单行道 3711 号车向东离开单行道 3712 号车向东进入单行道 3712 号车向东通过单行道,道上车数:1 3713 号车向东进入单行道 3713 号车向东通过单行道,道上车数:2 3712 号车向东离开单行道 3713 号车向东离开单行道 $ 六辆向东四辆向西,正常通过。
Dp.h #include <iostream> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/msg.h> #include <sys/wait.h> /*信号灯控制用的共同体*/ 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; // 一个方向阻塞队列 Sema* sema1; // 另一方向阻塞队列 Lock* lock; // 进入管程时获取的锁 }; class OneWay { public: OneWay (int maxall, int maxcur); ~OneWay(); void Arrive (int direc); // 车辆准备上单行道,direc 为行车方向 void Cross (int direc); // 车辆正在单行道上 void Quit (int direc); // 车辆通过了单行道 int *eastCount; int *westCount; int *eastWait; int *westWait; int *sumPassedCars;//已经通过的车辆总数 private: //建立或获取 ipc 信号量的一组函数的原型说明 int get_ipc_id (char *proc_file, key_t key); int set_sem(key_t sem_key, int sem_val, int sem_flag); //创建共享内存 char *set_shm(key_t shm_key, int shm_num, int shm_flag); int rate; //车速 int *maxCars;//最大同向车数 int *numCars; //当前正在通过的车辆数 int *currentDire;//当前通过的车辆的方向 Condition *condition; //通过单行道的条件变量 Lock *lock;//单行道管程锁 };
Dp.cpp #include "Dp.h" using namespace std; 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; } /* * 用于单行道管程的互斥执行 */ 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) { #define BUFSZ 256 FILE *pf; int i, j; char line[BUFSZ], colum[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] != ' ') colum[j++] = line[i++]; colum[j] = '\0'; if (atoi(colum) != key) continue; j = 0; while (line[i] == ' ') i++; while (line[i] != ' ') colum[j++] = line[i++]; colum[j] = '\0'; i = atoi(colum); fclose(pf); return i; } fclose(pf); return -1; } /* * 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; } /* * 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; } Condition::Condition(Sema *semax1, Sema *semax2) { sema0 = semax1; sema1 = semax2; } /** * 看看是否能通过 */ void Condition::Wait(Lock *lock, int direc) { if (direc == 0) { cout << getpid() << "号车向东等待单行道" << "\n"; lock->open_lock(); sema0->down(); lock->close_lock(); } else if (direc == 1) { cout << getpid() << "号车向西等待单行道" << "\n"; lock->open_lock(); sema1->down(); lock->close_lock(); } } int Condition::Signal (int direc) { int i; if (direc == 0) //唤醒一个方向 { i = sema0->up(); } else if (direc == 1) { i = sema1->up(); } return i; } /* * get_ipc_id() 从/proc/sysvipc/文件系统中获取 IPC 的 id 号 * pfile: 对应/proc/sysvipc/目录中的 IPC 文件分别为 * msg-消息队列,sem-信号量,shm-共享内存 * key: 对应要获取的 IPC 的 id 号的键值 */ Condition::~Condition() { } ; /* * set_shm 函数建立一个具有 n 个字节 的共享内存区 * 如果建立成功,返回 一个指向该内存区首地址的指针 shm_buf * 输入参数: * shm_key 共享内存的键值 * shm_val 共享内存字节的长度 * shm_flag 共享内存的存取权限 */ OneWay::OneWay (int maxall, int maxcur) { Sema *sema0; Sema *sema1; Sema *semaLock; 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); //当前方向 0 东 1 西 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_flg); int sema1_id = set_sem(402, 0, ipc_flg); int semaLock_id = set_sem(601, maxcur, ipc_flg); //初始化所有变量 *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); semaLock = new Sema(semaLock_id); lock = new Lock(semaLock); condition = new Condition(sema0, sema1); } void OneWay::Arrive (int direc) { lock->close_lock(); //如果当前方向不是我的方向或者单行道上有车且车辆数大于等于上限数 if ((*currentDire != direc || *numCars >= *maxCars) & *sumPassedCars > 0) { if (direc == 0) { *eastWait += 1; } else if (direc == 1) { *westWait += 1; } condition->Wait(lock, direc); } if (direc == 0) //东 +1 { *eastWait -= 1; *eastCount = *eastCount + 1; cout << getpid() << "号车向东进入单行道\n"; } else if (direc == 1) //西 +1 { *westCount = *westCount + 1; *westWait -= 1; cout << getpid() << "号车向西进入单行道\n"; } *numCars = *numCars + 1; *currentDire = direc; *sumPassedCars += 1; lock->open_lock(); } void OneWay::Cross (int direc) { lock->close_lock(); sleep(4); if (direc == 0) cout << getpid() << "号车向东通过单行道,道上车数:" << *numCars << "\n"; else if (direc == 1) cout << getpid() << "号车向西通过单行道,道上车数:" << *numCars << "\n"; lock->open_lock(); } void OneWay::Quit (int direc) { lock->close_lock(); *numCars -= 1; if (direc == 0) { cout << getpid() << "号车向东离开单行道" << "\n"; } else if (direc == 1) { cout << getpid() << "号车向西离开单行道" << "\n"; } 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); } } } lock->open_lock(); } OneWay::~OneWay() { delete condition; } int main (int argc, char **argv) { int maxCars; int maxSingelDirect; cout << "请输入总车辆数:"; cin >> maxCars; cout << "请输入单方向通过的最大车数:"; cin >> maxSingelDirect; OneWay *oneWay = new OneWay(maxCars, maxSingelDirect); //建立管程,判断可不可进、决定方向,进入单行道 int i; int pid[maxCars]; for (i = 0; i < maxCars; i++) //创建子进程 { pid[i] = fork(); if (pid[i] == 0) { sleep(1); srand(time(NULL)); int direct = rand() % 2; //决定东西方向 direct=*oneWay->sumPassedCars%2; oneWay->Arrive(direct); //进入 oneWay->Cross(direct); //通过 oneWay->Quit(direct); //离开 exit(EXIT_SUCCESS); } } for (i = 0; i < maxCars; i++) { waitpid(pid[i], NULL, 0); } cout << *(oneWay->eastCount) << "辆列车向东" << *(oneWay->westCount) << "辆列车向西,正常通行.\n"; delete oneWay; return EXIT_SUCCESS; }
makefile head = Dp.h src =Dp.cpp objs = Dp.o opts =-w -g -c all: Dp Dp: $(objs) g++ $(objs) -o Dp Dp.o: $(head) $(src) g++ $(opts) $(src) clean: rm Dp *.o