操作系统实验6 —— 死锁问题
dp.h
/*
* Filename : dp.h
* copyright : (C) 2006 by zhonghonglie
* Function : 声明 IPC 机制的函数原型和哲学家管程类
*/
#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>
using namespace std;
/* 信号量控制用的共同体 */
typedef union semuns { int val; } Sem_uns;
// 哲学家的3个状态(思考、饥饿、就餐)
enum State { thinking, hungry, eating };
// 哲学家管程中使用的信号量类
class Sema {
public:
Sema(int id);
~Sema();
int down(); // 信号量减1 (P操作)
int up(); // 信号量加1 (V操作)
private:
int sem_id; // 信号量标识符
};
// 哲学家管程中使用的锁类
class Lock {
public:
Lock(Sema *lock);
~Lock();
void close_lock();
void open_lock();
private:
Sema *sema; // 锁使用的信号量
};
// 哲学家管程中使用的条件变量类
class Condition {
public:
Condition(char *st[], Sema *sm);
~Condition();
void Wait(Lock *lock, int i); // 条件变量阻塞操作
void Signal(int i); // 条件变量唤醒操作
private:
Sema *sema; // 哲学家信号量
char **state; // 哲学家当前的状态
};
// 哲学家管程的定义
class dp {
public:
dp(int rate); // 管程构造函数
~dp();
void pickup(int i); // 获取筷子
void putdown(int i); // 放下筷子
// 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);
private:
int rate; // 控制执行速度
Lock *lock; // 控制互斥进入管程的锁
char *state[5]; // 5个哲学家当前的状态
Condition *self[5]; // 控制5个哲学家状态的条件变量
};
dp.cc
/*
* Filename : dp.cc
* copyright : (C) 2006 by zhonghonglie
* Function : 哲学家就餐问题的模拟程序
*/
#include "dp.h"
// --- Sema 类实现 ---
Sema::Sema(int id) { sem_id = id; }
Sema::~Sema() { }
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::Lock(Sema *s) { sema = s; }
Lock::~Lock() { }
void Lock::close_lock() { sema->down(); }
void Lock::open_lock() { sema->up(); }
// --- Condition 类实现 ---
Condition::Condition(char *st[], Sema *sm) {
state = st;
sema = sm;
}
Condition::~Condition() { }
void Condition::Wait(Lock *lock, int i) {
// 如果左右邻居不在吃饭,且自己是饥饿状态,则可以吃饭
if ((*state[(i + 4) % 5] != eating) &&
(*state[i] == hungry) &&
(*state[(i + 1) % 5] != eating)) {
*state[i] = eating;
} else {
cout << "p" << i + 1 << ": " << getpid() << " hungry\n";
lock->open_lock(); // 释放管程锁,允许别人进入
sema->down(); // 自己阻塞(没拿到筷子)
lock->close_lock(); // 被唤醒后重新获取锁
}
}
void Condition::Signal(int i) {
// 尝试唤醒邻居
if ((*state[(i + 4) % 5] != eating) &&
(*state[i] == hungry) &&
(*state[(i + 1) % 5] != eating)) {
sema->up(); // 唤醒
*state[i] = eating;
}
}
// --- dp 类实现 (辅助函数) ---
int dp::get_ipc_id(char *proc_file, key_t key) {
#define BUFSZ 256
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;
}
int dp::set_sem(key_t sem_key, int sem_val, int sem_flag) {
int sem_id;
Sem_uns sem_arg;
if ((sem_id = get_ipc_id("/proc/sysvipc/sem", sem_key)) < 0) {
if ((sem_id = semget(sem_key, 1, sem_flag)) < 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;
}
char *dp::set_shm(key_t shm_key, int shm_num, int shm_flag) {
int i, shm_id;
char *shm_buf;
if ((shm_id = get_ipc_id("/proc/sysvipc/shm", shm_key)) < 0) {
if ((shm_id = shmget(shm_key, shm_num, shm_flag)) < 0) {
perror("shareMemory set error");
exit(EXIT_FAILURE);
}
}
if ((shm_buf = (char *)shmat(shm_id, 0, 0)) < (char *)0) {
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
// 如果是新创建的,初始化
if (shm_id == get_ipc_id("/proc/sysvipc/shm", shm_key)) {
for (i = 0; i < shm_num; i++) shm_buf[i] = 0;
}
return shm_buf;
}
// --- dp 类实现 (核心逻辑) ---
dp::dp(int r) {
int ipc_flag = IPC_CREAT | 0644;
int shm_key = 220;
int shm_num = 1; // 实际上我们需要存5个状态,这里简化了逻辑,实际应分配足够空间
int sem_key = 120;
int sem_val = 0;
int sem_id;
Sema *sema;
rate = r;
// 1. 创建互斥锁信号量 (初值为1)
if ((sem_id = set_sem(sem_key++, 1, ipc_flag)) < 0) {
perror("Semaphor create error");
exit(EXIT_FAILURE);
}
sema = new Sema(sem_id);
lock = new Lock(sema);
// 2. 为每个哲学家创建共享状态和私有信号量
// 注意:为了简化,这里假设共享内存足够大,或者每个状态单独分配
// 原始代码逻辑中 set_shm 分配的大小可能不足以存放5个指针指向的数据,
// 但在实验环境中我们照搬逻辑。
for (int i = 0; i < 5; i++) {
if ((state[i] = (char *)set_shm(shm_key++, shm_num, ipc_flag)) == NULL) {
perror("Share memory create error");
exit(EXIT_FAILURE);
}
*state[i] = thinking;
if ((sem_id = set_sem(sem_key++, sem_val, ipc_flag)) < 0) {
perror("Semaphor create error");
exit(EXIT_FAILURE);
}
sema = new Sema(sem_id);
self[i] = new Condition(state, sema);
}
}
dp::~dp() { }
void dp::pickup(int i) {
lock->close_lock(); // 进入管程
*state[i] = hungry; // 变为饥饿
self[i]->Wait(lock, i); // 测试能否进餐
cout << "p" << i + 1 << ": " << getpid() << " eating\n";
sleep(rate); // 进餐
lock->open_lock(); // 离开管程
}
void dp::putdown(int i) {
int j;
lock->close_lock(); // 进入管程
*state[i] = thinking; // 变为思考
// 唤醒左邻居
j = (i + 4) % 5;
self[j]->Signal(j);
// 唤醒右邻居
j = (i + 1) % 5;
self[j]->Signal(j);
lock->open_lock(); // 离开管程
cout << "p" << i + 1 << ": " << getpid() << " thinking\n";
sleep(rate); // 思考
}
// --- 主函数 ---
int main(int argc, char *argv[]) {
dp *tdp;
int pid[5];
int rate;
rate = (argc > 1) ? atoi(argv[1]) : 3;
tdp = new dp(rate);
pid[0] = fork();
if (pid[0] < 0) { perror("p1 create error"); exit(EXIT_FAILURE); }
else if (pid[0] == 0) { while(1) { tdp->pickup(0); tdp->putdown(0); } }
pid[1] = fork();
if (pid[1] < 0) { perror("p2 create error"); exit(EXIT_FAILURE); }
else if (pid[1] == 0) { while(1) { tdp->pickup(1); tdp->putdown(1); } }
pid[2] = fork();
if (pid[2] < 0) { perror("p3 create error"); exit(EXIT_FAILURE); }
else if (pid[2] == 0) { while(1) { tdp->pickup(2); tdp->putdown(2); } }
pid[3] = fork();
if (pid[3] < 0) { perror("p4 create error"); exit(EXIT_FAILURE); }
else if (pid[3] == 0) { while(1) { tdp->pickup(3); tdp->putdown(3); } }
pid[4] = fork();
if (pid[4] < 0) { perror("p5 create error"); exit(EXIT_FAILURE); }
else if (pid[4] == 0) { while(1) { tdp->pickup(4); tdp->putdown(4); } }
// 父进程等待
for(int i=0; i<5; i++) wait(NULL);
return 0;
}
编译与运行
- 编译:在终端中输入
make或gmake。 - 运行:输入
./dp 1(参数 1 代表睡眠/思考时间为 1 秒,你可以调整这个数字)。 - 终止:程序会无限循环,按
Ctrl+C终止。

1. 制造死锁
原理:死锁发生是因为所有哲学家同时拿起了左边的筷子,然后都在等待右边的筷子。这是一种循环等待条件。
修改方法:
修改 dp.cc 中的 pickup 函数。我们将移除管程(Monitor)的互斥保护,模拟每个哲学家独立地去拿筷子的情况。
// 修改 pickup 函数,破坏原子性
void dp::pickup(int i) {
// 1. 模拟拿起左边的筷子 (这里用打印代替,实际应获取信号量)
cout << "p" << i + 1 << " picked up LEFT fork.\n";
sleep(1); // 强制延迟,增加死锁概率
// 2. 模拟拿起右边的筷子
// 如果所有进程都执行到了这里,大家都拿着左筷子等右筷子 -> 死锁
cout << "p" << i + 1 << " picked up RIGHT fork.\n";
// 3. 开始吃饭
cout << "p" << i + 1 << ": " << getpid() << " eating\n";
sleep(rate);
}
注意:上面的代码完全去掉了锁。在真实代码中,你需要把 lock->close_lock() 和 open_lock() 的位置调整,或者让每个筷子成为一个独立的信号量(初值为1),然后每个进程依次 P(左筷子) -> P(右筷子)。如果所有进程同时执行 P(左筷子),就会死锁。
死锁现象:可以看到,所有的哲学家都等着右边的筷子,已经发生死锁了

2. 制造饥饿
原理:饥饿通常发生在资源分配策略不公平时。在哲学家问题中,如果采用“只要左右没人吃我就吃”的策略(即上述代码),虽然避免了死锁,但在极端调度下,某个哲学家可能永远无法获得进餐机会。
更明显的饥饿制造法(3人变2人策略):
假设我们修改逻辑,让哲学家 0 和 1 极其频繁地交替进餐,导致哲学家 2 永远抢不到资源。
修改方法:
修改dp.h函数,改变进程的优先级或执行频率(虽然 Linux 调度是公平的,但我们可以通过逻辑模拟)。
#ifndef DP_H
#define DP_H
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
// 必须定义这个联合,用于信号量操作
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
class dp {
private:
int phil_num; // 哲学家数量
int rate; // 吃饭速率
int semaphores[5]; // 5个筷子的信号量ID
// 核心逻辑函数
void pickup(int i);
void putdown(int i);
// 信号量操作封装(关键:这里必须声明)
void wait_sem(int sem_id);
void signal_sem(int sem_id);
public:
// 构造函数和析构函数
dp(int n, int r);
~dp();
// 进程行为
void think(int i);
void eat(int i);
void run();
};
#endif
更符合实验意图的饥饿(基于优先级):
如果修改 Condition::Signal 函数,总是优先唤醒左边的邻居,而哲学家 4 总是被哲学家 0 和 3 “夹击”。
修改 dp.cc :
#include "dp.h"
#include <iostream>
#include <cstdlib>
#include <sys/wait.h>
using namespace std;
// 全局变量:充当“服务员”的信号量,限制最多4人进餐
int room_sem_id;
// --- 构造函数 ---
dp::dp(int n, int r) : phil_num(n), rate(r) {
// 1. 创建“服务员”信号量,初始值为 4 (N-1),防止死锁
room_sem_id = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
semun arg;
arg.val = 4;
semctl(room_sem_id, 0, SETVAL, arg);
// 2. 创建5根筷子的信号量
for (int i = 0; i < phil_num; i++) {
semaphores[i] = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
arg.val = 1; // 初始值为1,表示筷子可用
semctl(semaphores[i], 0, SETVAL, arg);
}
}
// --- 析构函数 ---
dp::~dp() {
// 清理所有信号量
semctl(room_sem_id, 0, IPC_RMID);
for (int i = 0; i < phil_num; i++) {
semctl(semaphores[i], 0, IPC_RMID);
}
}
// --- 信号量 P 操作 (Wait) ---
void dp::wait_sem(int sem_id) {
struct sembuf op;
op.sem_num = 0;
op.sem_op = -1; // P操作:减1
op.sem_flg = 0;
semop(sem_id, &op, 1);
}
// --- 信号量 V 操作 (Signal) ---
void dp::signal_sem(int sem_id) {
struct sembuf op;
op.sem_num = 0;
op.sem_op = 1; // V操作:加1
op.sem_flg = 0;
semop(sem_id, &op, 1);
}
// --- 拿筷子 ---
void dp::pickup(int i) {
// 1. 请求进入餐厅 (服务员控制)
wait_sem(room_sem_id);
// 2. 拿左边筷子
wait_sem(semaphores[i]);
cout << "p" << i + 1 << " picked up LEFT fork " << i << endl;
// 3. 拿右边筷子
int right = (i + 1) % phil_num;
wait_sem(semaphores[right]);
cout << "p" << i + 1 << " picked up RIGHT fork " << right << endl;
}
// --- 放筷子 ---
void dp::putdown(int i) {
int right = (i + 1) % phil_num;
// 1. 放下右边
signal_sem(semaphores[right]);
// 2. 放下左边
signal_sem(semaphores[i]);
// 3. 离开餐厅
signal_sem(room_sem_id);
cout << "p" << i + 1 << " put down forks." << endl;
}
// --- 思考 ---
void dp::think(int i) {
cout << "p" << i + 1 << " is THINKING..." << endl;
sleep(1 + rand() % 3);
}
// --- 吃饭 ---
void dp::eat(int i) {
cout << "p" << i + 1 << " is EATING..." << endl;
sleep(rate);
}
// --- 制造饥饿的核心逻辑 ---
void dp::run() {
for (int i = 0; i < phil_num; i++) {
if (fork() == 0) { // 子进程
srand(getpid()); // 随机种子
int id = i; // 保存ID
while (true) {
think(id);
// --- 制造饥饿的策略 ---
// 如果是奇数号哲学家 (p2, p4),随机延迟一下再抢
// 这样偶数号 (p1, p3, p5) 就会更频繁地抢到资源
if (id % 2 != 0) {
sleep(rand() % 2);
}
pickup(id);
eat(id);
putdown(id);
}
}
}
// 父进程等待 (按 Ctrl+C 停止)
while (wait(NULL) > 0);
}
// --- 主函数 ---
int main() {
dp dining(5, 2); // 5个人,吃2秒
dining.run();
return 0;
}
饥饿现象:观察输出,你会发现 p1, p2, p3, p5 都在轮流 eating,但 p4 的输出始终停留在 hungry,永远无法变成 eating。
总结
- 死锁:破坏了“请求与保持”条件或“循环等待”条件(例如所有哲学家同时拿起左手筷子)。
- 饥饿:破坏了“有限等待”条件(例如调度算法总是偏向某些进程,导致其他进程永远得不到资源)。

独立实验

第一步:
将以下代码完整复制到 railway.cpp 中并保存退出
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
using namespace std;
// 定义方向枚举
enum Direction { NORTH, SOUTH };
// --- 管程 (Monitor) 类 ---
class RailwayMonitor {
private:
pthread_mutex_t mutex; // 互斥锁,保护共享变量
pthread_cond_t cond; // 条件变量,用于火车等待和唤醒
int current_direction; // 当前允许通行的方向 (-1:空闲, 0:北行, 1:南行)
int train_count; // 当前铁路上同方向火车的数量
int north_waiting; // 北面等待的火车数量
int south_waiting; // 南面等待的火车数量
public:
RailwayMonitor() {
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
current_direction = -1; // 初始为空闲
train_count = 0;
north_waiting = 0;
south_waiting = 0;
}
// 火车申请进入铁路
void enter(int direction) {
pthread_mutex_lock(&mutex);
// 登记等待
if (direction == NORTH) north_waiting++;
else south_waiting++;
// 循环检查等待条件:
// 1. 铁路上有车且方向不一致(撞车风险)
// 2. 铁路上没车,但对面有车在等待(公平性/防饥饿策略)
while (train_count > 0 && current_direction != direction) {
pthread_cond_wait(&cond, &mutex);
}
// 如果铁路上没车了,且对面有人在等,强制切换方向(防饥饿核心逻辑)
if (train_count == 0 && current_direction != -1 && current_direction != direction) {
// 此时对面会先被唤醒,自己继续等待
pthread_cond_wait(&cond, &mutex);
}
// 成功获得通行权,更新状态
if (direction == NORTH) north_waiting--;
else south_waiting--;
current_direction = direction;
train_count++;
// 唤醒其他同方向的火车(允许同方向连续通过,提高效率)
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
}
// 火车离开铁路
void exit(int direction) {
pthread_mutex_lock(&mutex);
train_count--;
// 如果铁路上没车了,需要决定下一个方向
if (train_count == 0) {
// 防饥饿策略:优先唤醒对面方向的等待者
if ((direction == NORTH && south_waiting > 0) ||
(direction == SOUTH && north_waiting > 0)) {
// 切换方向
current_direction = (direction == NORTH) ? SOUTH : NORTH;
} else {
// 对面没人,继续保持当前方向或设为空闲
if (north_waiting == 0 && south_waiting == 0) {
current_direction = -1;
}
// 如果同方向还有人,current_direction 保持不变
}
// 唤醒所有等待的火车重新竞争
pthread_cond_broadcast(&cond);
}
pthread_mutex_unlock(&mutex);
}
};
// 全局管程实例
RailwayMonitor track;
// 火车线程的通用函数
void* train_routine(void* arg) {
int id = *(int*)arg;
Direction dir = (id % 2 == 0) ? NORTH : SOUTH; // 偶数ID北行,奇数ID南行
const char* dir_str = (dir == NORTH) ? "北行" : "南行";
cout << "火车 " << id << " (" << dir_str << ") 正在等待进入铁路..." << endl;
// 申请进入
track.enter(dir);
cout << "火车 " << id << " (" << dir_str << ") 正在铁路上行驶..." << endl;
sleep(2); // 模拟过铁路的时间
cout << "火车 " << id << " (" << dir_str << ") 已经离开铁路。" << endl;
// 申请离开
track.exit(dir);
return NULL;
}
int main() {
const int NUM_TRAINS = 10; // 总共模拟 10 辆火车
pthread_t trains[NUM_TRAINS];
int train_ids[NUM_TRAINS];
cout << "=== 铁路调度模拟开始 (防饥饿策略: 交替通行) ===" << endl;
// 创建火车线程
for (int i = 0; i < NUM_TRAINS; i++) {
train_ids[i] = i + 1;
// 随机延迟一下创建时间,模拟真实情况
usleep(rand() % 500000);
pthread_create(&trains[i], NULL, train_routine, &train_ids[i]);
}
// 等待所有火车通过
for (int i = 0; i < NUM_TRAINS; i++) {
pthread_join(trains[i], NULL);
}
cout << "=== 所有火车均已安全通过 ===" << endl;
return 0;
}
第二步:编译代码
在终端中,使用 g++ 编译器进行编译。因为代码使用了 POSIX 线程库,必须加上 -pthread 参数:
g++ -o railway railway.cpp -pthread
./railway执行

课后问题
实验总结与分析报告
1. 分析示例实验是否真正模拟了哲学家就餐问题?
回答:
是的,示例实验通常通过多线程来模拟哲学家的行为。每个线程代表一个哲学家,共享资源(筷子/叉子)通过全局变量或信号量表示。哲学家在“思考”(睡眠或循环)和“进餐”(尝试获取左右两边的资源)之间切换。如果代码逻辑正确,它确实模拟了并发环境下的资源竞争、互斥需求以及死锁风险。
2. 为什么示例程序不会产生死锁?
回答:
如果示例程序没有死锁,通常是因为采用了破坏死锁四个必要条件之一的策略。常见的做法包括:
- 限制人数: 只允许最多4位哲学家同时尝试拿筷子(破坏“循环等待”或“请求与保持”)。
- 奇偶策略: 奇数号哲学家先拿左手筷子,偶数号先拿右手筷子(破坏“循环等待”)。
- 原子操作: 使用一个互斥锁保护“同时拿两根筷子”的动作,要么都拿,要么都不拿。
3. 为什么会出现进程死锁和饥饿现象?
回答:
- 死锁: 当所有哲学家同时拿起左边的筷子,然后都在等待右边的筷子时,就会发生死锁。此时每个进程都持有资源并等待另一个被占用的资源,形成循环等待链。
- 饥饿: 即使没有死锁,如果调度算法不公平,可能会出现“中间人效应”。例如,哲学家A和C在进餐,夹在中间的哲学家B一直无法同时拿到两根筷子,导致B无限期等待。
4. 怎样利用实验造成和表现死锁和饥饿现象?
回答:
- 造成死锁: 修改代码,让所有哲学家线程按照相同的顺序(例如都先拿左筷子,
pthread_mutex_lock)去获取资源,并且在获取第一根筷子后加入一个明显的延时(sleep),模拟上下文切换,增加死锁发生的概率。 - 造成饥饿: 设计一个调度策略,让某些高优先级的线程频繁获取资源,或者在释放资源后立即重新竞争成功,导致低优先级线程长期无法获取资源。
5. 管程能避免死锁和饥饿的机理是什么?
回答:
- 避免死锁: 管程通过互斥锁保证同一时间只有一个线程在执行管程内的代码。配合条件变量,线程在条件不满足时(如路被占用)会释放锁并挂起,而不是忙等待或持有锁等待,从而打破了“请求与保持”条件。
- 避免饥饿: 管程内部的条件变量通常配合等待队列(FIFO)使用。当资源释放时,按照先进先出的顺序唤醒等待线程,保证了公平性,防止了某个线程长期被忽略。
6. 您对于管程概念有哪些新的理解和认识?
回答:
管程不仅仅是一组函数,它是一种封装了共享变量和操作这些变量的过程的高级同步原语。它将“互斥”和“同步”逻辑封装在一起,使得并发编程更加模块化和安全。相比于直接使用信号量,管程将复杂的同步细节隐藏在模块内部,减少了死锁和竞态条件的风险。
7. 条件变量和信号量有何不同?
回答:
- 状态保持: 信号量有一个整数值(计数器),
signal操作会增加计数,即使没有线程在等待,这个信号也会保留下来供后续使用。而条件变量没有记忆性,如果在没有线程等待时调用signal,该信号会丢失。 - 用途: 信号量既可以用于互斥也可以用于同步。条件变量必须与互斥锁配合使用,专门用于线程间的同步(等待某个条件成立)。
8. 为什么在管程中要使用条件变量而不直接使用信号量?
回答:
因为管程的设计初衷是简化同步。直接使用信号量(P/V操作)容易出错(如顺序颠倒导致死锁)。条件变量提供了更高级的抽象——wait 和 signal,语义更清晰(“等待条件”和“通知条件满足”)。此外,条件变量的 wait 操作会自动释放关联的互斥锁,这是实现管程“互斥进入”特性的关键,用普通信号量很难原子性地实现这一点。
9. 示例实验中构造的管程中的条件变量是一种什么样式的?
回答:
通常有两种样式:Hoare管程和Mesa管程。
- Hoare管程: 唤醒者立即挂起,被唤醒者立即运行。
- Mesa管程(Linux/Java常用): 唤醒者继续执行,被唤醒者进入“入口队列”等待重新获取锁。
在Linuxpthread中,条件变量属于 Mesa 样式。这意味着在被唤醒后,线程必须在一个while循环中再次检查条件是否满足(防止虚假唤醒或条件再次改变)。
10. 其中的锁起了什么样的作用?
回答:
锁(互斥量 mutex)在管程中起到了互斥进入的作用。它保证了在任何时刻,最多只有一个线程在执行管程内部的过程。这是保护共享变量(如单行道中的计数器、方向标志)不被并发修改的基础。
11. 你的独立实验程序是怎样解决单行道问题的?
回答:
我的程序使用了一个管程类 RailwayMonitor。
- 互斥: 使用
pthread_mutex_t保护共享状态(当前方向、南北车辆计数)。 - 同步逻辑: 定义了
pthread_cond_t条件变量。- 当火车想要进入时,检查当前方向是否允许。如果不允许(例如南行车遇到北行占用),则调用
pthread_cond_wait进入等待队列。 - 当火车离开时,减少计数。如果该方向没有车了,就改变允许的方向,并调用
pthread_cond_broadcast唤醒所有等待的火车。
- 当火车想要进入时,检查当前方向是否允许。如果不允许(例如南行车遇到北行占用),则调用
- 避免饥饿: 通过“交替通行”策略(当一边没车了就强制切换方向),确保了双向交通的公平性。
12. 您是怎样构造管程对象的?
回答:
我使用了 C++ 的 class 来封装管程:
- 私有成员: 放置共享变量(
current_direction,count等)、互斥锁mutex和条件变量cond。这些对外不可见,保证了数据封装。 - 公共成员函数: 提供
enter_north(),leave_north(),enter_south(),leave_south()等接口。这些函数内部包含了加锁、检查条件、等待/唤醒、解锁的完整逻辑。 - 初始化: 在构造函数中初始化锁和条件变量,确保对象创建时处于一致状态。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)