1. 为什么在操作系统中引入进程同步机制?
进程并发执行时,可能因共享资源 / 协作执行 出现 "竞态条件"(结果依赖执行顺序),或因协作逻辑需要协调执行步骤。引入同步机制是为了保证进程执行的正确性、共享资源的有序访问,避免数据不一致或逻辑错误。
2. 有哪些方法可以实现进程的互斥与同步?
信号量机制(记录型、整型)
管程机制
互斥锁
条件变量
消息传递机制
3. 比较各种进程同步方法的优缺点?
| 方法 | 优点 | 缺点 |
|---|---|---|
| 信号量 | 灵活,支持复杂同步逻辑 | 易出错(如 PV 操作顺序、死锁) |
| 管程 | 封装性好,更安全 | 实现较复杂,灵活性略低 |
| 互斥锁 | 实现简单,效率高 | 仅支持互斥,不支持复杂同步 |
| 消息传递 | 天然支持进程间通信 | 同步逻辑需依赖消息交互,开销略高 |
4. 实现进程互斥的基本原理是什么?
核心是保证 "临界资源" 在同一时刻仅被一个进程访问。通过同步机制(如信号量、锁)让进程进入 "临界区" 前先申请权限,访问完释放权限,从而避免并发冲突。
5. 说明记录型信号量的物理意义?
记录型信号量包含两个成员:
value:表示可用资源的数量(>0:可用资源数;=0:无可用资源;<0:等待队列中进程数的绝对值);queue:存储因该资源阻塞的进程队列。
物理意义是:通过 value 管理资源,通过队列管理等待进程,实现资源的有序分配与进程同步。
6. 为什么在生产者 - 消费者问题中 wait () 操作的顺序不能颠倒?
以 "生产者" 为例,若先执行 wait(empty)(申请空缓冲区)、再执行 wait(mutex)(申请互斥锁)的顺序颠倒:若先 wait(mutex) 获得锁,再 wait(empty) 但空缓冲区不足时,进程会阻塞,且持有互斥锁不释放 ,导致其他进程(生产者 / 消费者)无法访问临界资源,引发死锁。
7. wait () 和 signal () 操作都必须是原子操作的确切含义是什么?为什么必须是原子操作?
原子操作的含义 :wait()/signal() 的执行过程不会被其他进程 / 线程打断,要么完整执行,要么不执行。
原因 :若操作非原子(如 wait() 中修改 value 的过程被打断),会导致信号量状态不一致,进而引发资源竞争、数据错误或死锁(比如多个进程同时 "误以为" 获得了资源)。
8. 信号量机制 wait () 和 signal () 的实现过程 + 经典同步问题的同步算法
wait (S) 实现:
将信号量 S.value 减 1;
若 S.value < 0,则将当前进程加入 S.queue,并阻塞该进程。
signal (S) 实现:
将信号量 S.value 加 1;
若 S.value ≤ 0,则从 S.queue 唤醒一个进程,将其插入就绪队列。
经典同步问题(如生产者 - 消费者) :设信号量 mutex=1(互斥访问缓冲区)、empty=N(空缓冲区数)、full=0(满缓冲区数)。
生产者:
wait(empty);
wait(mutex);
// 向缓冲区放数据
signal(mutex);
signal(full);
消费者:
wait(full);
wait(mutex);
// 从缓冲区取数据
signal(mutex);
signal(empty);
9. 什么是管程?举例说明引入管程有什么好处?
管程 :是一种封装了共享资源和同步操作的机制,进程通过调用管程的过程(函数)来访问资源,管程内部保证同一时刻仅一个进程执行其过程(自动实现互斥)。
好处 :例如 "生产者 - 消费者" 用管程实现时,无需手动管理 mutex 信号量,管程自动保证互斥;同时通过条件变量实现同步,代码更简洁、安全(避免 PV 操作顺序错误)。
10. 利用管程解决哲学家进餐问题的管程及哲学家进程
管程 DiningPhilosopher :包含条件变量 forks[5](表示叉子是否可用)、过程 pickup(i)(拿第 i 个哲学家的叉子)、putdown(i)(放叉子)。
monitor DiningPhilosopher {
condition forks[5];
// 拿叉子:需同时拿到左右叉
procedure pickup(int i) {
if (叉子i或i+1被占用)
wait(forks[i]);
// 标记叉子为占用
}
// 放叉子:释放左右叉并唤醒等待的哲学家
procedure putdown(int i) {
// 标记叉子为可用
signal(forks[(i+1)%5]);
signal(forks[i]);
}
}
哲学家进程:
while (true) {
思考;
DiningPhilosopher.pickup(i);
进餐;
DiningPhilosopher.putdown(i);
}
11. 三个进程 PA、PB、PC 的文件打印同步(记录型信号量)
设信号量:
s1=1(缓冲区 1 的互斥 / 初始可用)、s2=0(缓冲区 2 初始无数据)、s3=0(缓冲区 2 数据已处理 / 初始无数据)。
PA 进程:
while (true) {
从磁盘读记录到缓冲区1;
signal(s2); // 通知PB缓冲区1有数据
wait(s1); // 等待PB取走数据(缓冲区1可用)
}
PB 进程:
while (true) {
wait(s2); // 等待PA写入缓冲区1
复制缓冲区1到缓冲区2;
signal(s1); // 通知PA缓冲区1可用
signal(s3); // 通知PC缓冲区2有数据
}
PC 进程:
while (true) {
wait(s3); // 等待PB写入缓冲区2
打印缓冲区2内容;
// 无需signal,因为缓冲区2仅PC使用(或按需加信号量)
}
12. 线程 P(写温度)和 Q(读温度)的同步问题
(1) 数据区 B 的结构 :采用队列(如先进先出队列),因为 Q 需要 "最新的检测数据",队列可按顺序存储数据,Q 取队尾(最新)数据。
(2) 同步关系:
- P 和 Q互斥访问 B 区(避免读写冲突);
- Q 需等待 P 写入数据(B 区为空时 Q 阻塞)。
(3) 记录型信号量实现 :设信号量 mutex=1(互斥访问 B 区)、has_data=0(B 区是否有数据)。
线程 P:
while (true) {
检测温度;
wait(mutex);
将温度写入B区;
signal(mutex);
signal(has_data); // 通知Q有新数据
}
线程 Q:
while (true) {
wait(has_data); // 无数据则阻塞
wait(mutex);
从B区取出最新温度;
signal(mutex);
处理温度数据;
}
13. 进程通信方式 + 管道的定义与特点
进程通信方式:包括管道、消息队列、共享内存、信号量、套接字等。
管道 :是一种基于文件的半双工通信机制,用于父子 / 亲缘进程间通信,数据以字节流形式传输。
- 匿名管道:特点:仅用于亲缘进程;无文件名,通过文件描述符访问;随进程退出而销毁。
- 命名管道:特点:可用于任意进程;有文件名(存在于文件系统);独立于进程生命周期(需手动删除)。
14. 消息缓冲队列机制的实现原理
消息缓冲队列是基于消息的通信机制,原理:
- 发送进程将消息封装为消息缓冲区(含目标进程 ID、消息长度、内容等);
- 通过
send()原语,将消息缓冲区插入目标进程的消息队列(需互斥访问队列,用信号量实现); - 接收进程通过
receive()原语,从自己的消息队列中取出消息缓冲区,读取内容; - 若消息队列为空,接收进程会阻塞,直到有消息到来。