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

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()

关键逻辑说明

  1. signal.set_wakeup_fd(wfh):Python会在收到信号时自动向该文件描述符写入一个字节(信号编号),无需手动在信号处理函数中写管道
  2. 设置管道写端为非阻塞:避免信号处理时写入管道阻塞
  3. 空信号处理函数:仅保留信号注册,实际事件通过管道IO感知

内容的提问来源于stack exchange,提问作者Jonas Müller

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 17:40:31