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

子进程创建前后注册signal.SIGTERM的差异及原因探究

Python signal模块进程终止异常问题

问题描述

使用Python的signal模块管理进程时遇到异常:希望通过b.py向运行中的a.py发送SIGTERM信号,终止其主进程与子进程。但发现将signal.signal(signal.SIGTERM, handle_signal)放在__main__入口前时,子进程无法被正常终止;放在子进程启动后则可正常终止。请问这是Python机制还是操作系统设计导致的?

a.py

import multiprocessing
import os
import signal
import time


def process1():
    while True:
        print("子进程正在运行")
        time.sleep(1)


def handle_signal(signum, frame):
    print("收到信号:", signum, "进程ID:", os.getpid())


# 注册信号位置1
signal.signal(signal.SIGTERM, handle_signal)  # 放在这里时子进程无法正常终止
if "__main__" == __name__:
    a = multiprocessing.Process(target=process1)
    a.daemon = True
    a.start()

    # 注册信号位置2
    # signal.signal(signal.SIGTERM, handle_signal)  # 放在这里时可正常终止

    child_pid = a.pid
    parent_pid = os.getpid()

    parent_pid_group = os.getpgid(parent_pid)

    with open("./crawler.pid", "w") as f:
        f.write(str(parent_pid_group))

    a.join()

    print("父进程ID:", parent_pid)
    print("子进程ID:", child_pid)
    print("所有进程已终止!")

b.py

import os
import signal

with open("./crawler.pid", "r") as f:
    try:
        pid = int(f.read())
        os.killpg(pid, signal.SIGTERM)
        print("已成功向进程组", pid, "发送信号")
    except Exception as e:
        print("发送信号出错:", e)

错误输出(信号注册在子进程启动前):

╰─ python a.py
子进程正在运行
子进程正在运行
子进程正在运行
子进程正在运行
子进程正在运行
收到信号: 15 进程ID: 1379
收到信号: 15 进程ID: 1380
子进程正在运行
子进程正在运行
子进程正在运行
子进程正在运行
^CTraceback (most recent call last):
  File "a.py", line 34, in <module>
    a.join()
  File "/usr/lib/python3.8/multiprocessing/process.py", line 149, in join
    res = self._popen.wait(timeout)
  File "/usr/lib/python3.8/multiprocessing/popen_fork.py", line 47, in wait
    return self.poll(os.WNOHANG if timeout == 0.0 else 0)
  File "/usr/lib/python3.8/multiprocessing/popen_fork.py", line 27, in poll
    pid, sts = os.waitpid(self.pid, flag)
KeyboardInterrupt
收到信号: 15 进程ID: 1380
Process Process-1:
Traceback (most recent call last):
  File "/usr/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
  File "/usr/lib/python3.8/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "a.py", line 10, in process1
    time.sleep(1)
KeyboardInterrupt

----------------------
╰─ python b.py 
已成功向进程组 1379 发送信号

正确输出(信号注册在子进程启动后):

╰─ python a.py
子进程正在运行
子进程正在运行
子进程正在运行
收到信号: 15 进程ID: 1961
父进程ID: 1961
子进程ID: 1962
所有进程已终止!

---------------------
╰─ python b.py 
已成功向进程组 1961 发送信号

问题原因

这是Python多进程的fork机制结合操作系统进程创建逻辑导致的:

  1. fork进程的复制特性:在Unix/Linux系统中,Python的multiprocessing默认用fork创建子进程,fork会完整复制父进程的地址空间,包括已注册的信号处理函数。

  2. 信号注册时机的影响:

    • 当信号注册放在__main__入口前时,子进程fork时会继承这个自定义信号处理函数。但你的handle_signal仅打印信号信息,没有执行进程退出逻辑,所以子进程收到SIGTERM后只会打印日志,继续运行;父进程则因a.join()阻塞等待子进程,导致整个进程组无法正常终止。
    • 当信号注册放在子进程启动后,子进程fork时还未继承这个处理函数,子进程的SIGTERM会使用系统默认处理逻辑(直接终止进程)。父进程收到SIGTERM后,a.join()因守护进程终止而结束,随后父进程正常退出。
  3. 守护进程的特性补充:你设置了a.daemon = True,意味着父进程退出时会自动终止该守护子进程。这也是信号注册在子进程启动后,整个进程组能正常终止的关键原因之一。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 20:00:05