基于C语言管道实现Dining Philosophers的消息传递故障排查
你的哲学家就餐程序管道与同步问题分析&修复
嘿,我瞅了你的代码和运行输出,问题主要出在管道的使用逻辑混乱和多进程下共享变量的误解上,这俩问题直接导致你的管道消息传不出来,同步逻辑也完全失效。下面给你掰扯清楚:
首先说管道的核心问题
你现在的管道操作完全走偏了:
- 子进程把stdout重定向到管道写端,本来是想把状态输出发给主进程,但主进程倒好,每fork一个子进程就把自己的stdin换成这个管道的读端——这不仅会把之前的管道读端覆盖,而且主进程根本没去读这些管道里的内容!子进程的printf输出全堵在管道缓冲区里,要么卡着要么丢了,这就是你看不到哲学家状态输出的直接原因。
- 管道的分工错了:主进程应该保留所有管道的读端,子进程保留写端,而不是主进程乱改自己的stdin。
然后是更致命的多进程同步问题
你用了全局的信号量spoon、phil[N]和状态数组state[N],但多进程的全局变量是各自独立的副本啊!每个哲学家进程都有自己的一套信号量和state,等于说:
sem_wait(&spoon)根本实现不了全局互斥,每个进程都在等自己的spoon,完全没起到同步作用。test(i)判断的是当前进程自己的state,不是其他哲学家的状态,所以永远没法正确判断左右邻居是不是在吃饭。
修复后的完整代码
我给你改好了,核心是用共享内存让所有进程共享信号量和状态,同时修正了管道的使用逻辑:
#include <stdio.h> #include <stdlib.h> #include <semaphore.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h> #define N 5 #define LEFT (i + 4) % N #define RIGHT (i + 1) % N #define THINKING 0 #define HUNGRY 1 #define EATING 2 // 把需要共享的变量打包成结构体,放进共享内存 typedef struct { sem_t spoon; sem_t phil[N]; int state[N]; } SharedData; SharedData *shared; // 指向共享内存的指针 int fd[N][2]; // 每个子进程对应一个管道 pid_t pids[N]; void philosopher(int i); void test(int i); void takeSpoon(int i); void putSpoon(int i); int main() { // 创建共享内存,大小刚好放下我们的共享结构体 int shmid = shmget(IPC_PRIVATE, sizeof(SharedData), IPC_CREAT | 0666); if (shmid == -1) { perror("shmget failed"); exit(EXIT_FAILURE); } shared = (SharedData *)shmat(shmid, NULL, 0); if (shared == (void *)-1) { perror("shmat failed"); exit(EXIT_FAILURE); } // 初始化共享信号量:第二个参数设为1,代表进程间共享 sem_init(&shared->spoon, 1, 1); for (int i = 0; i < N; i++) { sem_init(&shared->phil[i], 1, 0); shared->state[i] = THINKING; } // 创建管道+fork子进程 for (int i = 0; i < N; i++) { if (pipe(fd[i]) == -1) { perror("pipe failed"); exit(EXIT_FAILURE); } pids[i] = fork(); if (pids[i] == 0) { // 子进程:关读端,把stdout重定向到管道写端 close(fd[i][0]); dup2(fd[i][1], STDOUT_FILENO); close(fd[i][1]); philosopher(i); exit(0); } else if (pids[i] == -1) { perror("fork failed"); exit(EXIT_FAILURE); } else { // 主进程:关写端,留着读端准备读子进程输出 close(fd[i][1]); } } // 主进程用select监听所有管道,实时读取子进程的输出 fd_set read_fds; char buffer[256]; while (1) { FD_ZERO(&read_fds); int max_fd = 0; // 把所有有效的管道读端加入监听集合 for (int i = 0; i < N; i++) { if (fd[i][0] != -1) { FD_SET(fd[i][0], &read_fds); if (fd[i][0] > max_fd) { max_fd = fd[i][0]; } } } int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (ret == -1) { perror("select failed"); break; } // 遍历所有管道,读取有数据的管道 for (int i = 0; i < N; i++) { if (FD_ISSET(fd[i][0], &read_fds)) { ssize_t n = read(fd[i][0], buffer, sizeof(buffer)-1); if (n <= 0) { // 子进程退出了,关掉这个管道读端 close(fd[i][0]); fd[i][0] = -1; } else { buffer[n] = '\0'; printf("[哲学家 %d] %s", i+1, buffer); } } } // 检查所有子进程是否都退出了,是的话就结束循环 int all_exited = 1; for (int i = 0; i < N; i++) { if (waitpid(pids[i], NULL, WNOHANG) == 0) { all_exited = 0; break; } } if (all_exited) { break; } } // 清理资源:销毁信号量, detach共享内存,删除共享内存 for (int i = 0; i < N; i++) { sem_destroy(&shared->phil[i]); } sem_destroy(&shared->spoon); shmdt(shared); shmctl(shmid, IPC_RMID, NULL); return 0; } void philosopher(int i) { while (1) { printf("正在思考\n"); sleep(1); takeSpoon(i); printf("正在吃饭\n"); sleep(2); putSpoon(i); } } void takeSpoon(int i) { sem_wait(&shared->spoon); shared->state[i] = HUNGRY; printf("饿了,想吃饭\n"); test(i); sem_post(&shared->spoon); sem_wait(&shared->phil[i]); } void putSpoon(int i) { sem_wait(&shared->spoon); shared->state[i] = THINKING; printf("放下叉子 %d 和 %d\n", LEFT + 1, i + 1); test(LEFT); test(RIGHT); sem_post(&shared->spoon); } void test(int i) { if (shared->state[i] == HUNGRY && shared->state[LEFT] != EATING && shared->state[RIGHT] != EATING) { shared->state[i] = EATING; printf("拿起叉子 %d 和 %d\n", LEFT + 1, i + 1); sem_post(&shared->phil[i]); } }
重点改了啥?
共享内存搞定进程同步:
- 用
shmget创建共享内存,shmat挂载到进程地址空间,把信号量和状态数组放进去,这样所有进程都访问同一套变量,同步逻辑才生效。 - 信号量初始化时第二个参数设为
1,明确告诉系统这个信号量是给进程间用的。
- 用
管道逻辑彻底修正:
- 主进程留着所有管道的读端,子进程把输出重定向到管道写端,这样子进程的printf能通过管道传给主进程。
- 用
select实现多路监听,主进程能同时接收所有子进程的输出,不会漏也不会堵。
其他小修复:
- 改了
putSpoon里的拼写错误(原代码puts downspoon少了空格,这里换成中文状态提示更直观,你也可以改回英文)。 - 加了系统调用的错误处理,方便你排查问题。
- 改了
现在运行这个程序,就能看到每个哲学家的状态变化,管道消息传递正常,同步逻辑也能正确避免死锁啦~
内容的提问来源于stack exchange,提问作者Alonzo Ouzts II
相关产品推荐
相关产品推荐

