You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C语言两子进程向父进程发信号,父进程回复时遇死锁/等待锁问题

问题分析与修复方案

看起来你的代码里有几个关键问题导致了死锁和未定义行为,我来一步步拆解并修复:

核心问题梳理

  • 子进程层级错误:你现在是让第一个子进程再fork出第二个子进程,导致第二个是父进程的孙子进程,父进程无法直接管理它(比如waitpid会失败)
  • 信号发送方向错误:子进程没有向父进程发送指定的SIGUSR1/SIGUSR2,反而给其他子进程发SIGTERM,完全不符合需求
  • 管道时序颠倒:子进程先阻塞在read(),根本没机会发送信号给父进程,直接造成死锁
  • 父进程信号等待逻辑缺失:只调用一次pause(),无法等待两个子进程的信号
  • 异步信号不安全操作:信号处理函数里用printf,可能引发未定义行为

修复后的完整代码

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>

// 异步信号安全的打印函数
void safe_printf(const char *msg) {
    write(STDOUT_FILENO, msg, strlen(msg));
}

// 信号处理函数,仅做标记(避免不安全操作)
volatile sig_atomic_t sigusr1_received = 0;
volatile sig_atomic_t sigusr2_received = 0;

void handler(int signumber) {
    if (signumber == SIGUSR1) {
        sigusr1_received = 1;
        safe_printf("Received SIGUSR1\n");
    } else if (signumber == SIGUSR2) {
        sigusr2_received = 1;
        safe_printf("Received SIGUSR2\n");
    }
}

int main() {
    // 注册信号处理函数
    signal(SIGUSR1, handler);
    signal(SIGUSR2, handler);

    // 为每个子进程创建独立管道(避免读写冲突)
    int pipe1[2], pipe2[2];
    if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
        perror("Pipe creation failed");
        exit(EXIT_FAILURE);
    }

    pid_t child1, child2;

    // 创建第一个子进程
    child1 = fork();
    if (child1 == 0) {
        // 子进程1:关闭管道写端(只读)
        close(pipe1[1]);
        // 睡眠3秒后向父进程发SIGUSR1
        safe_printf("Child 1: Waiting 3s, will send SIGUSR1 to parent\n");
        sleep(3);
        kill(getppid(), SIGUSR1);
        // 等待父进程的回复
        char buf[100] = {0};
        ssize_t bytes_read = read(pipe1[0], buf, sizeof(buf)-1);
        if (bytes_read > 0) {
            safe_printf("Child 1 received: ");
            safe_printf(buf);
            safe_printf("\n");
        }
        close(pipe1[0]);
        safe_printf("Child 1 exited\n");
        exit(EXIT_SUCCESS);
    }

    // 创建第二个子进程
    child2 = fork();
    if (child2 == 0) {
        // 子进程2:关闭管道写端(只读)
        close(pipe2[1]);
        // 睡眠3秒后向父进程发SIGUSR2
        safe_printf("Child 2: Waiting 3s, will send SIGUSR2 to parent\n");
        sleep(3);
        kill(getppid(), SIGUSR2);
        // 等待父进程的回复
        char buf[100] = {0};
        ssize_t bytes_read = read(pipe2[0], buf, sizeof(buf)-1);
        if (bytes_read > 0) {
            safe_printf("Child 2 received: ");
            safe_printf(buf);
            safe_printf("\n");
        }
        close(pipe2[0]);
        safe_printf("Child 2 exited\n");
        exit(EXIT_SUCCESS);
    }

    // 父进程逻辑:等待两个信号都收到
    safe_printf("Parent: Waiting for signals from children...\n");
    while (!sigusr1_received || !sigusr2_received) {
        pause(); // 阻塞直到收到信号
    }

    // 向两个子进程分别发送回复
    close(pipe1[0]); // 关闭父进程的读端
    close(pipe2[0]);
    const char *msg1 = "Hello from parent to Child 1";
    const char *msg2 = "Hello from parent to Child 2";
    write(pipe1[1], msg1, strlen(msg1)+1); // 包含结束符
    write(pipe2[1], msg2, strlen(msg2)+1);
    // 关闭写端,让子进程read()收到EOF
    close(pipe1[1]);
    close(pipe2[1]);

    // 等待两个子进程退出
    int status;
    waitpid(child1, &status, 0);
    waitpid(child2, &status, 0);

    safe_printf("Parent process exited\n");
    return 0;
}

关键修改说明

  1. 子进程创建方式:父进程直接fork两次,创建两个独立的子进程,确保父进程持有两个子进程的PID,能正常等待它们退出。
  2. 独立管道:给每个子进程分配一个单独的管道,避免读写冲突,确保每个子进程能收到专属的回复。
  3. 信号逻辑修正:子进程先发送信号给父进程(用getppid()获取父进程PID),再等待管道回复,颠倒了之前的时序,解决死锁。
  4. 信号等待逻辑:用两个原子变量标记信号是否收到,父进程循环pause()直到两个信号都到达。
  5. 异步信号安全:替换printfwrite实现的safe_printf,避免信号处理函数中的不安全操作。
  6. 管道正确关闭:父进程写完后立即关闭管道写端,让子进程的read()能正常返回,避免无限阻塞。

运行效果

编译运行后,你会看到类似如下输出:

Parent: Waiting for signals from children...
Child 1: Waiting 3s, will send SIGUSR1 to parent
Child 2: Waiting 3s, will send SIGUSR2 to parent
Received SIGUSR1
Received SIGUSR2
Child 1 received: Hello from parent to Child 1
Child 1 exited
Child 2 received: Hello from parent to Child 2
Child 2 exited
Parent process exited

内容的提问来源于stack exchange,提问作者Shapperd

火山引擎 最新活动