子进程创建前后注册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机制结合操作系统进程创建逻辑导致的:
fork进程的复制特性:在Unix/Linux系统中,Python的
multiprocessing默认用fork创建子进程,fork会完整复制父进程的地址空间,包括已注册的信号处理函数。信号注册时机的影响:
- 当信号注册放在
__main__入口前时,子进程fork时会继承这个自定义信号处理函数。但你的handle_signal仅打印信号信息,没有执行进程退出逻辑,所以子进程收到SIGTERM后只会打印日志,继续运行;父进程则因a.join()阻塞等待子进程,导致整个进程组无法正常终止。 - 当信号注册放在子进程启动后,子进程fork时还未继承这个处理函数,子进程的SIGTERM会使用系统默认处理逻辑(直接终止进程)。父进程收到SIGTERM后,
a.join()因守护进程终止而结束,随后父进程正常退出。
- 当信号注册放在
守护进程的特性补充:你设置了
a.daemon = True,意味着父进程退出时会自动终止该守护子进程。这也是信号注册在子进程启动后,整个进程组能正常终止的关键原因之一。
内容的提问来源于stack exchange,提问作者Runstone
相关产品推荐
相关产品推荐

