You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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]);
    }
}

重点改了啥?

  1. 共享内存搞定进程同步:

    • 用shmget创建共享内存,shmat挂载到进程地址空间,把信号量和状态数组放进去,这样所有进程都访问同一套变量,同步逻辑才生效。
    • 信号量初始化时第二个参数设为1,明确告诉系统这个信号量是给进程间用的。
  2. 管道逻辑彻底修正:

    • 主进程留着所有管道的读端,子进程把输出重定向到管道写端,这样子进程的printf能通过管道传给主进程。
    • 用select实现多路监听,主进程能同时接收所有子进程的输出,不会漏也不会堵。
  3. 其他小修复:

    • 改了putSpoon里的拼写错误(原代码puts downspoon少了空格,这里换成中文状态提示更直观,你也可以改回英文)。
    • 加了系统调用的错误处理,方便你排查问题。

现在运行这个程序,就能看到每个哲学家的状态变化,管道消息传递正常,同步逻辑也能正确避免死锁啦~

内容的提问来源于stack exchange,提问作者Alonzo Ouzts II

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:45:34