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

基于信号的进程同步异常:无usleep则失效的问题排查

进程间信号同步失效的原因与可靠解决方案

嘿,这个问题我太熟了——你踩中了多进程同步里典型的时序竞态陷阱,而usleep(1000)只是碰巧让时序“对齐”了,并不是真正的解决方案。

先拆解下你的场景:父进程启动后fork子进程,然后立刻发信号,但这时候子进程可能还没完成信号处理函数的注册,甚至还没来得及调用pause()等待信号。这时候信号会直接触发默认行为(比如SIGUSR2默认是终止进程),或者因为子进程还没注册处理函数,信号被丢弃,导致你的同步逻辑完全失效。

而加了usleep(1000)之后,父进程会短暂等待,给子进程足够的时间完成信号处理函数的注册并进入pause()状态,这时候再发信号,子进程就能正常捕获并处理,看起来就“正常运行”了。但这种方式非常不可靠——系统负载高的时候,1ms的延迟可能不够,问题会再次复现。

靠谱的解决方案:用同步机制替代延迟

要从根本上解决这个问题,你需要让父进程确认子进程已经准备好接收信号之后再发送信号,这里给你两种常用的方案:

方案1:用管道做同步(最简单可靠)

利用管道的阻塞特性,让子进程完成信号注册后,通过管道给父进程发一个“就绪”信号,父进程收到后再发送目标信号:

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

int flag = -99;
int sync_pipe[2];

// 父进程信号处理函数
void parent_sig_handler(int sig) {
    flag = 1;
    printf("父进程捕获SIGUSR1,flag更新为:%d\n", flag);
}

// 子进程信号处理函数
void child_sig_handler(int sig) {
    flag = 2;
    printf("子进程捕获SIGUSR2,flag更新为:%d\n", flag);
}

int main() {
    // 创建同步管道
    if (pipe(sync_pipe) == -1) {
        perror("pipe 创建失败");
        exit(EXIT_FAILURE);
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork 失败");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) { // 子进程逻辑
        close(sync_pipe[0]); // 关闭管道读端

        // 注册信号处理函数
        signal(SIGUSR2, child_sig_handler);

        // 告诉父进程:我准备好了
        char ready_flag = '1';
        write(sync_pipe[1], &ready_flag, 1);
        close(sync_pipe[1]);

        // 等待信号
        pause();
        exit(EXIT_SUCCESS);
    } else { // 父进程逻辑
        close(sync_pipe[1]); // 关闭管道写端

        // 注册信号处理函数
        signal(SIGUSR1, parent_sig_handler);

        // 等待子进程的就绪通知(阻塞直到收到)
        char ready_flag;
        read(sync_pipe[0], &ready_flag, 1);
        close(sync_pipe[0]);

        // 现在子进程肯定准备好了,发送信号
        kill(pid, SIGUSR2);
        wait(NULL); // 等待子进程退出
        printf("最终flag值:%d\n", flag);
        exit(EXIT_SUCCESS);
    }
}

方案2:用sigsuspend替代pause(更灵活的信号控制)

sigsuspend可以让进程在等待信号时临时修改信号掩码,避免信号在等待前到达。你可以先阻塞目标信号,等子进程注册完处理函数后,再解除阻塞并等待:

// 核心逻辑片段(子进程部分)
sigset_t mask, old_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR2);

// 先阻塞SIGUSR2,避免信号提前到达
sigprocmask(SIG_BLOCK, &mask, &old_mask);

// 注册信号处理函数
signal(SIGUSR2, child_sig_handler);

// 告诉父进程我准备好了(可以用管道或者其他方式)
// ... 这里省略管道同步的代码,和方案1类似 ...

// 解除SIGUSR2的阻塞并等待信号,原子操作,不会有竞态
sigsuspend(&old_mask);

关键总结

  • 你的初始代码失效,本质是信号发送时机早于子进程的信号处理准备时机,属于典型的竞态条件。
  • usleep是治标不治本的临时方案,依赖系统环境,不可靠。
  • 必须用管道、信号量或者sigsuspend这类原子同步机制,确保信号发送时接收方已经准备就绪。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:43:28