Python子进程信号同步问题:解决信号丢失致进程挂起方案
Python子进程信号同步问题与解决方案
需求
实现一个Python子进程,循环执行以下逻辑:
- 初始化进程
- 等待父进程发送的
SIGUSR1(start信号) - 处理数据并写入共享内存
- 向父进程发送
SIGUSR1(finish信号) - 返回等待信号步骤,循环执行
初始实现代码
import signal import os signal_received = False def signal_handler(signo, _): if signo == signal.SIGUSR1: signal_received = True def main(): global signal_received signal.signal(signal.SIGUSR1, signal_handler) while True: # solution attempt: # signal.sigsuspend(...) while not signal_received: signal.pause() signal_received = False process() # solution attempt: # signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGUSR1]) os.kill(os.getppid(), signal.SIGUSR1)
问题分析
该实现存在信号丢失问题:如果父进程的SIGUSR1信号在while not signal_received检查之后、signal.pause()执行之前的短窗口内到达,信号会被处理但signal_received标记还未重置,导致后续signal.pause()永久挂起。
在C语言中,可通过sigmask与sigsuspend组合原子性地解除信号阻塞并等待,避免这个窗口,但Python标准库未提供sigsuspend接口。
自管道技巧的尝试(未解决)
尝试用自管道技巧将信号事件转为IO事件,但子进程仍随机挂起——疑似信号发送时os.read(rfh, 1)已执行,导致信号处理函数未触发写入管道。
子进程代码
import signal import os rfh, wfh = os.pipe() def signal_handler(signo, _): if signo == signal.SIGUSR1: os.write(wfh, b"\x00") def main(): signal.signal(signal.SIGUSR1, signal_handler) while True: os.kill(os.getppid(), signal.SIGUSR1) os.read(rfh, 1) # foo() (some processing) if __name__ == "__main__": main()
父进程C代码
#include <unistd.h> #include <signal.h> #include <iostream> static volatile bool signal_received = false; void handle(int signo, siginfo_t *, void *) { if (signo == SIGUSR1) signal_received = true; } int main() { // Setup signal handler struct sigaction act = {0}; act.sa_flags = SA_SIGINFO | SA_RESTART; act.sa_sigaction = &handle; sigaction(SIGUSR1, &act, NULL); pid_t pid = fork(); if (pid == 0) { char path[] = "/usr/bin/python3"; char *const argv[] = {"python3", "main.py", NULL}; char *const env[] = {NULL}; execve(path, argv, env); } sigset_t mask, oldmask; sigemptyset(&mask); sigaddset(&mask, SIGUSR1); while (true) { static int iteration = 0; std::cout << iteration++ << std::endl; sigprocmask(SIG_BLOCK, &mask, &oldmask); while (!signal_received) sigsuspend(&oldmask); signal_received = false; sigprocmask(SIG_UNBLOCK, &mask, NULL); kill(pid, SIGUSR1); } return 0; }
跨平台解决方案(适配macOS和Linux)
通过signal.set_wakeup_fd将信号事件直接关联到管道,结合非阻塞管道配置,完美解决信号丢失问题:
import signal import os import fcntl rfh, wfh = os.pipe() fcntl.fcntl(wfh, fcntl.F_SETFL, os.O_NONBLOCK) def main(): signal.signal(signal.SIGUSR1, lambda *_: None) signal.set_wakeup_fd(wfh) while True: os.kill(os.getppid(), signal.SIGUSR1) os.read(rfh, 1) # 此处添加数据处理与共享内存写入逻辑 if __name__ == "__main__": main()
关键逻辑说明
signal.set_wakeup_fd(wfh):Python会在收到信号时自动向该文件描述符写入一个字节(信号编号),无需手动在信号处理函数中写管道- 设置管道写端为非阻塞:避免信号处理时写入管道阻塞
- 空信号处理函数:仅保留信号注册,实际事件通过管道IO感知
内容的提问来源于stack exchange,提问作者Jonas Müller
相关产品推荐
相关产品推荐

