C语言多子进程fork循环异常排查:养老院模拟程序问题
问题修复:多进程养老院模拟程序的同步与共享资源问题
我先梳理下你代码里导致进程无法并行运行甚至仅单次执行的核心问题,再给出保留原结构和术语的修复方案:
核心问题分析
- 进程间资源完全隔离:
fork创建的子进程会复制父进程的内存空间,所以每个子进程修改的medicamentos、camas和idosos数组都是各自的独立副本——相当于每个老人在自己的“独立养老院”里活动,彼此完全看不到对方的操作,逻辑彻底混乱。 - 无同步机制的竞争条件:就算资源能共享,多个进程同时读写这些变量会触发竞争(比如两个进程同时检查
camas >=1,然后都去占床位,导致床位数量变成负数)。 - 随机数未初始化:
rand()没有设置种子,所有进程生成的随机数完全一致,导致老人行为高度重复甚至卡住。 - 父进程提前退出:原
main函数创建完子进程后直接退出,子进程会变成孤儿进程,可能被系统提前终止。 - 床位不足时无等待逻辑:
dormir函数里如果床位不够就直接跳过操作,导致老人一直处于ESPERANDO状态,无法继续流程。
修复方案
我们用System V共享内存让所有子进程共享状态变量,用System V信号量实现资源同步,同时修复逻辑漏洞:
修复后的完整代码
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/wait.h> #include <time.h> #define ACORDADO 0 // awake #define ESPERANDO 1 // waiting #define DORMINDO 2 // sleeping #define N 7 // 打包所有需要共享的变量到结构体 typedef struct { int medicamentos; int camas; int idosos[N]; } SharedData; SharedData *shared; // 共享内存指针 int sem_id; // 信号量ID // 信号量P操作(获取锁) void sem_p() { struct sembuf sb = {0, -1, 0}; semop(sem_id, &sb, 1); } // 信号量V操作(释放锁) void sem_v() { struct sembuf sb = {0, 1, 0}; semop(sem_id, &sb, 1); } void mostrar(void); // show info void dormir(int); // sleep void tomar_med(int); // take meds void esperar(int); // wait void acordar(int); // wake up void liberar_cama(int); // leave the bed void acao_idoso(int j); // elder action void forker(int n_proc) { pid_t pid; if (n_proc > 0) { if ((pid = fork()) < 0) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程初始化唯一随机种子 srand(time(NULL) + getpid()); acao_idoso(n_proc - 1); exit(EXIT_SUCCESS); } else { // 父进程继续创建下一个子进程 forker(n_proc - 1); } } } int main() { key_t key = ftok(".", 'R'); // 生成共享资源的唯一标识 if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); } // 创建共享内存 int shm_id = shmget(key, sizeof(SharedData), IPC_CREAT | 0666); if (shm_id == -1) { perror("shmget"); exit(EXIT_FAILURE); } // 挂载共享内存到进程地址空间 shared = (SharedData *)shmat(shm_id, NULL, 0); if (shared == (void *)-1) { perror("shmat"); exit(EXIT_FAILURE); } // 初始化共享资源初始状态 shared->medicamentos = 90; shared->camas = 5; memset(shared->idosos, ACORDADO, sizeof(shared->idosos)); // 创建互斥信号量(初始值1,确保同一时间只有一个进程访问共享资源) sem_id = semget(key, 1, IPC_CREAT | 0666); if (sem_id == -1) { perror("semget"); exit(EXIT_FAILURE); } semctl(sem_id, 0, SETVAL, 1); // 创建7个子进程 forker(7); // 父进程等待所有子进程执行完毕 int status; for (int i = 0; i < N; i++) { wait(&status); } // 清理共享内存和信号量 shmdt(shared); shmctl(shm_id, IPC_RMID, NULL); semctl(sem_id, 0, IPC_RMID); return 0; } void mostrar(void) { sem_p(); // 获取锁,避免输出时资源被修改 printf("=== 当前养老院状态 ===\n"); for (int i = 0; i < N; i++) { if (shared->idosos[i] == ACORDADO) printf("O idoso %d esta acordado.\n", i + 1); else if (shared->idosos[i] == ESPERANDO) printf("O idoso %d esta esperando.\n", i + 1); else if (shared->idosos[i] == DORMINDO) printf("O idoso %d esta dormindo.\n", i + 1); } printf("\n%d medicamentos sobrando.\n", shared->medicamentos); printf("%d camas disponiveis.\n\n", shared->camas); sem_v(); // 释放锁 } void acao_idoso(int j) { int i = j; while (1) { sem_p(); int meds_left = shared->medicamentos; sem_v(); if (meds_left <= 0) break; // 药品耗尽,退出循环 acordar(i); mostrar(); esperar(i); mostrar(); // 床位不足时循环等待,直到有空闲床位 while (1) { sem_p(); int beds_available = shared->camas; int state = shared->idosos[i]; sem_v(); if (state == ESPERANDO && beds_available >= 1) { dormir(i); break; } sleep(1); // 等待1秒后重试 } mostrar(); liberar_cama(i); mostrar(); } } void acordar(int i) { sem_p(); shared->idosos[i] = ACORDADO; sem_v(); // 随机清醒1-11秒(用秒模拟小时,方便测试) int t = rand() % 11; sleep(t + 1); } void esperar(int i) { sem_p(); if (shared->idosos[i] == ACORDADO && shared->medicamentos > 0) { shared->idosos[i] = ESPERANDO; } sem_v(); } void dormir(int i) { sem_p(); if (shared->idosos[i] == ESPERANDO && shared->camas >= 1) { if (i % 2 == 0) { // 偶数老人吃2粒睡8秒 if (shared->medicamentos >= 2) { shared->medicamentos -= 2; sem_v(); sleep(8); sem_p(); } else { sem_v(); return; // 药品不足,放弃本次睡眠 } } else { // 奇数老人吃1粒睡4秒 if (shared->medicamentos >= 1) { shared->medicamentos -= 1; sem_v(); sleep(4); sem_p(); } else { sem_v(); return; // 药品不足,放弃本次睡眠 } } shared->camas -= 1; shared->idosos[i] = DORMINDO; } sem_v(); } void liberar_cama(int i) { sem_p(); if (shared->idosos[i] == DORMINDO) { shared->idosos[i] = ACORDADO; shared->camas++; } sem_v(); }
关键修复点说明
- 共享内存实现资源共享:用
SharedData结构体打包所有状态变量,通过shmget和shmat让所有子进程访问同一块内存,确保操作同步。 - 信号量避免竞争:用互斥信号量控制共享资源的读写,同一时间只有一个进程能修改或读取状态,彻底解决竞争条件。
- 随机数唯一初始化:每个子进程用
time(NULL)+getpid()作为种子,保证每个老人的行为随机且独立。 - 床位等待逻辑:在老人等待阶段添加循环,直到有空闲床位再执行睡眠操作,避免进程卡住。
- 父进程等待子进程:
main函数用wait()循环等待所有子进程结束,避免子进程变成孤儿被系统提前终止。 - 资源清理:程序结束后释放共享内存和信号量,避免系统资源泄漏。
现在这个程序可以正确模拟7个老人并行抢床位、取药的场景,直到药品耗尽为止。
内容的提问来源于stack exchange,提问作者Luiza Pinheiro
相关产品推荐
相关产品推荐

