练习题
题目:详细比较进程和线程的区别,并说明它们各自的应用场景。
解题思路:
从资源分配、地址空间、并发粒度、故障影响四个维度对照,再按隔离性与共享需求选择。
详细步骤:
进程与线程的区别:
| 特征 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 独立的内存空间 | 共享进程的内存空间 |
| 创建开销 | 大(需要分配内存) | 小(共享已有资源) |
| 切换开销 | 大(需要切换内存空间) | 小(只需切换寄存器) |
| 通信方式 | 进程间通信(IPC) | 共享内存、全局变量 |
| 并发性 | 进程级并发 | 线程级并发 |
| 独立性 | 完全独立 | 相对独立 |
| 故障影响 | 一个进程故障不影响其他进程 | 一个线程故障可能影响整个进程 |
应用场景:
进程适用于:
- 需要高安全性和隔离性的应用
- 需要独立内存空间的程序
- 系统服务程序
- 需要故障隔离的场景
线程适用于:
- 需要高并发性能的应用
- 共享大量数据的程序
- 需要快速响应的用户界面
- 计算密集型任务的分割
最终答案:
进程是资源分配与调度的基本单位,拥有独立的地址空间;线程是 CPU 调度的基本单位,同一进程内的线程共享地址空间与资源,因此进程间通信要用共享内存、全局变量等手段。进程适用于需要高安全性和隔离性、独立内存空间、系统服务程序、需要故障隔离的场景;线程适用于需要高并发性能、共享大量数据、快速响应的用户界面与计算密集型任务的分割。
题目:画出进程的三种基本状态转换图,并说明每种转换的触发条件。
解题思路:
先画出三种基本状态与四条转换边,再逐条写清触发条件。
详细步骤:
进程状态转换图:
状态转换条件:
-
就绪 → 运行:
- CPU 调度器选择该进程执行
- 进程获得 CPU 时间片
- 更高优先级进程让出 CPU
-
运行 → 就绪:
- 时间片用完
- 被更高优先级进程抢占
- 进程主动让出 CPU
-
运行 → 阻塞:
- 进程请求 I/O 操作
- 等待某个事件或资源
- 进程调用阻塞系统调用
-
阻塞 → 就绪:
- 等待的事件发生
- 请求的资源可用
- I/O 操作完成
实际例子:
- 用户程序等待键盘输入:运行 → 阻塞
- 磁盘 I/O 完成:阻塞 → 就绪
- 时间片用完:运行 → 就绪
- 调度器选择进程:就绪 → 运行
最终答案:
三种基本状态是就绪、运行、阻塞。转换及触发条件为:就绪→运行(调度器选中该进程并分配时间片);运行→就绪(时间片用完、被更高优先级进程抢占、主动让出 CPU);运行→阻塞(请求 I/O、等待事件或资源、调用阻塞系统调用);阻塞→就绪(等待的事件发生、资源可用、I/O 完成)。
题目:比较先来先服务(FCFS)、短作业优先(SJF)和时间片轮转(RR)三种调度算法的特点,并分析它们的适用场景。
解题思路:
把三种算法放在同一张表里对比平均周转时间、平均等待时间、响应时间、公平性与实现复杂度,再逐个说适用场景。
详细步骤:
算法特点比较:
| 算法 | 平均周转时间 | 平均等待时间 | 响应时间 | 公平性 | 实现复杂度 |
|---|---|---|---|---|---|
| FCFS | 长 | 长 | 差 | 好 | 简单 |
| SJF | 短 | 短 | 中等 | 差 | 中等 |
| RR | 中等 | 中等 | 好 | 好 | 中等 |
详细分析:
1. 先来先服务(FCFS):
- 优点:实现简单,公平性好
- 缺点:平均等待时间长,对短进程不利,可能产生饥饿
- 适用场景:批处理系统,进程长度相近的系统
2. 短作业优先(SJF):
- 优点:平均周转时间最短,系统吞吐量高
- 缺点:对长进程不公平,需要预知进程长度
- 适用场景:批处理系统,进程长度差异较大的系统
3. 时间片轮转(RR):
- 优点:响应时间好,公平性好,适合交互式系统
- 缺点:上下文切换开销大,时间片选择影响性能
- 适用场景:分时系统,交互式系统
时间片选择的影响:
- 时间片太小:上下文切换频繁,系统开销大
- 时间片太大:退化为 FCFS,响应时间差
- 最佳时间片:10-100ms
最终答案:
FCFS 简单公平,但长作业排在后面时平均等待时间可能很长,适合批处理;SJF 平均周转时间最短、系统吞吐量高,但需要预知进程长度且对长进程不公平,适合批处理且进程长度差异大的系统;RR 响应时间好、公平性好,适合交互式与分时系统,但上下文切换开销大——时间片太小会频繁切换、系统开销大,太大则退化为 FCFS、响应时间差,最佳时间片约 10–100ms。
题目:什么是信号量?P/V 操作的含义是什么?请用信号量解决生产者-消费者问题。
解题思路:
先定义信号量的两种原子操作 P/V 的语义,再用三个信号量(mutex/empty/full)把生产者与消费者的顺序固定下来。
详细步骤:
信号量概念: 信号量是一个整型变量,除了初始化外,只能通过两个原子操作访问:
- P 操作(wait):申请资源,信号量减 1
- V 操作(signal):释放资源,信号量加 1
P/V 操作含义:
- P 操作:如果信号量大于 0,则减 1 并继续;如果为 0,则进程阻塞等待
- V 操作:信号量加 1,如果有进程在等待,则唤醒一个进程
生产者-消费者问题解决方案:
#define N 100
semaphore mutex = 1; // 互斥信号量
semaphore empty = N; // 空缓冲区数量
semaphore full = 0; // 满缓冲区数量
// 生产者
void producer() {
while (true) {
item = produce_item();
P(&empty); // 等待空缓冲区
P(&mutex); // 进入临界区
insert_item(item);
V(&mutex); // 退出临界区
V(&full); // 增加满缓冲区数量
}
}
// 消费者
void consumer() {
while (true) {
P(&full); // 等待满缓冲区
P(&mutex); // 进入临界区
item = remove_item();
V(&mutex); // 退出临界区
V(&empty); // 增加空缓冲区数量
consume_item(item);
}
}信号量类型:
- 二元信号量:值为 0 或 1,用于互斥
- 计数信号量:值可以为任意非负整数,用于同步
最终答案:
信号量是除了初始化外只能通过两个原子操作访问的整型量:P 操作(wait)申请资源、值减 1,V 操作(signal)释放资源、值加 1。互斥用二元信号量(mutex=1),同步用计数信号量(empty=n、full=0)。生产者顺序为 P(empty)→P(mutex)→放入产品→V(mutex)→V(full),消费者对称地 P(full)→P(mutex)→取出产品→V(mutex)→V(empty)。
题目:死锁的四个必要条件是什么?如何避免死锁?请举例说明。
解题思路:
先列出四个必要条件,再说明只要破坏其中任意一个就能预防死锁,最后给出银行家算法与有序资源申请两种做法。
详细步骤:
死锁的四个必要条件:
- 互斥条件:资源不能被多个进程同时使用
- 请求和保持条件:进程在等待其他资源时,不释放已占有的资源
- 不可剥夺条件:资源不能被强制剥夺
- 循环等待条件:存在进程等待链,形成循环
死锁避免方法:
1. 破坏互斥条件:
- 使用可共享的资源
- 使用虚拟化技术
- 使用资源池
2. 破坏请求和保持条件:
- 一次性分配所有资源
- 申请新资源前释放所有已占有资源
3. 破坏不可剥夺条件:
- 允许资源抢占
- 设置资源优先级
- 使用超时机制
4. 破坏循环等待条件:
- 资源有序分配
- 资源编号策略
- 资源层次结构
实际例子:
哲学家进餐问题的死锁情况:
- 所有哲学家同时拿起左边的筷子
- 然后等待右边的筷子
- 形成循环等待
解决方案:
// 使用资源有序分配
void philosopher(int i) {
int left = i;
int right = (i + 1) % 5;
// 总是先申请编号小的筷子
if (left < right) {
acquire_chopstick(left);
acquire_chopstick(right);
} else {
acquire_chopstick(right);
acquire_chopstick(left);
}
eat();
release_chopstick(left);
release_chopstick(right);
}银行家算法:
- 在分配资源前检查是否会导致不安全状态
- 只有在安全状态下才分配资源
- 需要预知进程的最大资源需求
最终答案:
四个必要条件是互斥条件、请求和保持条件、不可剥夺条件、循环等待条件。避免死锁就是破坏其中任意一条:破坏互斥要求资源可共享,一般做不到;破坏请求与保持要求一次性申请全部资源;破坏不可剥夺要求资源可被强制抢占;破坏循环等待要求按固定顺序申请资源——哲学家就餐问题中按编号顺序取筷子即可避免循环。银行家算法则是另一种思路:在分配前检查是否仍处于安全状态,只有安全才分配,但需要预知进程的最大资源需求。
题目:请用信号量解决读者-写者问题,要求读者可以同时读取,写者必须独占访问。
解题思路:
读者-写者的难点在于让读者可并发、写者独占,分别用 readcount 与 writecount 两个计数信号量配合一个互斥量来实现。
详细步骤:
读者-写者问题:
- 多个读者可以同时读取共享数据
- 写者必须独占访问
- 读者和写者不能同时访问
读者优先解决方案:
semaphore mutex = 1; // 保护readcount
semaphore wrt = 1; // 写者互斥
int readcount = 0; // 读者数量
// 读者
void reader() {
P(&mutex); // 进入临界区
readcount++;
if (readcount == 1)
P(&wrt); // 第一个读者,阻止写者
V(&mutex); // 退出临界区
read_data(); // 读取数据
P(&mutex); // 进入临界区
readcount--;
if (readcount == 0)
V(&wrt); // 最后一个读者,允许写者
V(&mutex); // 退出临界区
}
// 写者
void writer() {
P(&wrt); // 等待写者互斥锁
write_data(); // 写入数据
V(&wrt); // 释放写者互斥锁
}写者优先解决方案:
semaphore mutex = 1; // 保护readcount
semaphore wrt = 1; // 写者互斥
semaphore read = 1; // 读者队列
int readcount = 0; // 读者数量
int writecount = 0; // 写者数量
// 读者
void reader() {
P(&read); // 等待读者队列
P(&mutex);
readcount++;
if (readcount == 1)
P(&wrt); // 第一个读者,阻止写者
V(&mutex);
V(&read); // 释放读者队列
read_data();
P(&mutex);
readcount--;
if (readcount == 0)
V(&wrt); // 最后一个读者,允许写者
V(&mutex);
}
// 写者
void writer() {
P(&mutex);
writecount++;
if (writecount == 1)
P(&read); // 第一个写者,阻止读者
V(&mutex);
P(&wrt);
write_data();
V(&wrt);
P(&mutex);
writecount--;
if (writecount == 0)
V(&read); // 最后一个写者,允许读者
V(&mutex);
}两种方案的区别:
- 读者优先:读者可以连续进入,写者可能饥饿
- 写者优先:写者优先,读者可能饥饿
最终答案:
读者-写者问题用信号量解决:mutex 保护 readcount 与 writecount 的更新,read 记录当前读者数,wrt 保证写者独占访问。读者优先方案中读者只要 readcount 未跨越阈值就不阻塞进入,代价是写者可能饥饿;写者优先方案额外引入队列信号量,让写者先于新读者获得访问权,代价是读者可能饥饿。两种方案的本质区别就在于保证谁不饥饿。
题目:请实现一个简单的优先级调度算法,包括进程结构定义和调度函数。
解题思路:
先定义进程结构(含 PID、优先级、到达时间、所需时间),再实现「每次从未运行进程中挑选优先级最高者」的调度函数。
详细步骤:
进程结构定义:
typedef struct {
int pid; // 进程ID
char name[32]; // 进程名
int priority; // 优先级
int arrival_time; // 到达时间
int burst_time; // 执行时间
int remaining_time; // 剩余时间
int state; // 进程状态
int start_time; // 开始时间
int completion_time; // 完成时间
int turnaround_time; // 周转时间
int waiting_time; // 等待时间
} Process;
// 进程状态
#define READY 0
#define RUNNING 1
#define BLOCKED 2
#define COMPLETED 3优先级调度算法实现:
#define MAX_PROCESSES 100
Process processes[MAX_PROCESSES];
int process_count = 0;
int current_time = 0;
// 添加进程
void add_process(int pid, char* name, int priority, int arrival_time, int burst_time) {
if (process_count < MAX_PROCESSES) {
processes[process_count].pid = pid;
strcpy(processes[process_count].name, name);
processes[process_count].priority = priority;
processes[process_count].arrival_time = arrival_time;
processes[process_count].burst_time = burst_time;
processes[process_count].remaining_time = burst_time;
processes[process_count].state = READY;
processes[process_count].start_time = -1;
processes[process_count].completion_time = -1;
processes[process_count].turnaround_time = -1;
processes[process_count].waiting_time = -1;
process_count++;
}
}
// 找到最高优先级的就绪进程
int find_highest_priority_ready_process() {
int highest_priority = -1;
int selected_process = -1;
for (int i = 0; i < process_count; i++) {
if (processes[i].state == READY &&
processes[i].arrival_time <= current_time &&
processes[i].priority > highest_priority) {
highest_priority = processes[i].priority;
selected_process = i;
}
}
return selected_process;
}
// 优先级调度主函数
void priority_scheduling() {
int completed = 0;
int current_process = -1;
while (completed < process_count) {
// 检查是否有新进程到达
for (int i = 0; i < process_count; i++) {
if (processes[i].arrival_time == current_time) {
processes[i].state = READY;
}
}
// 如果当前没有进程运行,选择新进程
if (current_process == -1 || processes[current_process].state != RUNNING) {
current_process = find_highest_priority_ready_process();
if (current_process != -1) {
processes[current_process].state = RUNNING;
if (processes[current_process].start_time == -1) {
processes[current_process].start_time = current_time;
}
}
}
// 执行当前进程
if (current_process != -1) {
processes[current_process].remaining_time--;
// 检查进程是否完成
if (processes[current_process].remaining_time == 0) {
processes[current_process].state = COMPLETED;
processes[current_process].completion_time = current_time;
processes[current_process].turnaround_time =
processes[current_process].completion_time -
processes[current_process].arrival_time;
processes[current_process].waiting_time =
processes[current_process].turnaround_time -
processes[current_process].burst_time;
completed++;
current_process = -1;
}
}
current_time++;
}
}
// 打印调度结果
void print_scheduling_results() {
printf("进程调度结果:\n");
printf("PID\t名称\t优先级\t到达时间\t执行时间\t开始时间\t完成时间\t周转时间\t等待时间\n");
for (int i = 0; i < process_count; i++) {
printf("%d\t%s\t%d\t%d\t\t%d\t\t%d\t\t%d\t\t%d\t\t%d\n",
processes[i].pid,
processes[i].name,
processes[i].priority,
processes[i].arrival_time,
processes[i].burst_time,
processes[i].start_time,
processes[i].completion_time,
processes[i].turnaround_time,
processes[i].waiting_time);
}
}使用示例:
int main() {
// 添加测试进程
add_process(1, "P1", 3, 0, 6);
add_process(2, "P2", 1, 2, 4);
add_process(3, "P3", 4, 4, 2);
add_process(4, "P4", 2, 6, 8);
// 执行调度
priority_scheduling();
// 打印结果
print_scheduling_results();
return 0;
}最终答案:
实现分为三部分:① 用结构体维护 pid、name、priority、arrival_time、required_time 等字段构成进程表;② 调度函数每次遍历进程表,在已到达但未完成的进程中选出优先级最高者投入运行;③ 配套的增删进程与结果打印辅助函数。主函数演示添加四个进程后调用调度函数并打印结果。
题目:请实现一个简单的死锁检测算法,使用资源分配图方法。
解题思路:
死锁检测与银行家算法相反:它不预防,而是事后用资源分配图找环——把进程与资源建成二分图,看是否存在环。
详细步骤:
资源分配图结构:
#define MAX_PROCESSES 10
#define MAX_RESOURCES 10
typedef struct {
int process_id;
int resource_id;
int allocation; // 分配数量
int request; // 请求数量
} Edge;
typedef struct {
int process_count;
int resource_count;
Edge edges[MAX_PROCESSES * MAX_RESOURCES];
int edge_count;
int available[MAX_RESOURCES];
int total[MAX_RESOURCES];
} ResourceAllocationGraph;死锁检测算法实现:
// 初始化资源分配图
void init_graph(ResourceAllocationGraph* graph, int processes, int resources) {
graph->process_count = processes;
graph->resource_count = resources;
graph->edge_count = 0;
for (int i = 0; i < resources; i++) {
graph->available[i] = 0;
graph->total[i] = 0;
}
}
// 添加资源分配边
void add_allocation_edge(ResourceAllocationGraph* graph, int process_id, int resource_id, int amount) {
graph->edges[graph->edge_count].process_id = process_id;
graph->edges[graph->edge_count].resource_id = resource_id;
graph->edges[graph->edge_count].allocation = amount;
graph->edges[graph->edge_count].request = 0;
graph->edge_count++;
}
// 添加资源请求边
void add_request_edge(ResourceAllocationGraph* graph, int process_id, int resource_id, int amount) {
graph->edges[graph->edge_count].process_id = process_id;
graph->edges[graph->edge_count].resource_id = resource_id;
graph->edges[graph->edge_count].allocation = 0;
graph->edges[graph->edge_count].request = amount;
graph->edge_count++;
}
// 检查进程是否可以完成
bool can_process_complete(ResourceAllocationGraph* graph, int process_id) {
for (int i = 0; i < graph->edge_count; i++) {
if (graph->edges[i].process_id == process_id &&
graph->edges[i].request > 0) {
// 检查是否有足够的可用资源
if (graph->edges[i].request > graph->available[graph->edges[i].resource_id]) {
return false;
}
}
}
return true;
}
// 释放进程占有的资源
void release_process_resources(ResourceAllocationGraph* graph, int process_id) {
for (int i = 0; i < graph->edge_count; i++) {
if (graph->edges[i].process_id == process_id &&
graph->edges[i].allocation > 0) {
graph->available[graph->edges[i].resource_id] += graph->edges[i].allocation;
graph->edges[i].allocation = 0;
}
}
}
// 死锁检测算法
bool detect_deadlock(ResourceAllocationGraph* graph) {
bool finish[MAX_PROCESSES];
int work[MAX_RESOURCES];
// 初始化
for (int i = 0; i < graph->process_count; i++) {
finish[i] = false;
}
for (int i = 0; i < graph->resource_count; i++) {
work[i] = graph->available[i];
}
// 查找可以完成的进程
bool found;
do {
found = false;
for (int i = 0; i < graph->process_count; i++) {
if (!finish[i] && can_process_complete(graph, i)) {
// 释放进程资源
for (int j = 0; j < graph->edge_count; j++) {
if (graph->edges[j].process_id == i &&
graph->edges[j].allocation > 0) {
work[graph->edges[j].resource_id] += graph->edges[j].allocation;
}
}
finish[i] = true;
found = true;
}
}
} while (found);
// 检查是否所有进程都完成
for (int i = 0; i < graph->process_count; i++) {
if (!finish[i]) {
return true; // 存在死锁
}
}
return false; // 不存在死锁
}
// 打印资源分配图
void print_graph(ResourceAllocationGraph* graph) {
printf("资源分配图:\n");
printf("进程\t资源\t分配\t请求\n");
for (int i = 0; i < graph->edge_count; i++) {
printf("P%d\tR%d\t%d\t%d\n",
graph->edges[i].process_id,
graph->edges[i].resource_id,
graph->edges[i].allocation,
graph->edges[i].request);
}
printf("可用资源:");
for (int i = 0; i < graph->resource_count; i++) {
printf("R%d=%d ", i, graph->available[i]);
}
printf("\n");
}使用示例:
int main() {
ResourceAllocationGraph graph;
// 初始化图
init_graph(&graph, 3, 3);
// 设置可用资源
graph.available[0] = 1;
graph.available[1] = 1;
graph.available[2] = 0;
// 添加资源分配和请求
add_allocation_edge(&graph, 0, 0, 1); // P0持有R0
add_allocation_edge(&graph, 1, 1, 1); // P1持有R1
add_request_edge(&graph, 0, 1, 1); // P0请求R1
add_request_edge(&graph, 1, 0, 1); // P1请求R0
// 打印图
print_graph(&graph);
// 检测死锁
if (detect_deadlock(&graph)) {
printf("检测到死锁!\n");
} else {
printf("没有检测到死锁。\n");
}
return 0;
}最终答案:
检测用资源分配图实现:定义最大进程数与最大资源数的数据结构,用 add_edge 记录进程持有资源、add_request_edge 记录进程请求资源,随后在图上做环检测(进程→资源→进程的回路),发现环即判定存在死锁。示例中让 P0 持有 R1、P1 持有 R0 并互相请求,从而构造出死锁环。
