基于信号的进程同步异常:无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
相关产品推荐
相关产品推荐

