父进程终止时避免子进程存活:Flask+multiprocessing部署问题求助
这确实是在Web服务中用multiprocessing跑后台长任务的常见坑——当Flask父进程因为部署重启被终止时,子进程会脱离父进程的管理,变成系统接管的孤儿进程,继续占用CPU、内存等资源。下面给你几个实用的解决方案,你可以根据自己的场景选择:
方案1:子进程主动监听父进程状态
让子进程定期检查父进程是否存活,一旦发现父进程已经终止,就主动退出。这种方法轻量无依赖,适合简单场景。
修改你的function_wrapper函数,加入父进程存活检查逻辑:
import os import time def function_wrapper(self, parameters, config, process_dir): parent_pid = os.getppid() # 可根据任务类型调整检查间隔,比如10秒一次 check_interval = 10 while True: # 发送0号信号判断父进程是否存活:该信号不会执行任何操作,仅用于检测进程存在性 try: os.kill(parent_pid, 0) except OSError: # 父进程已终止,子进程主动退出 print(f"父进程{parent_pid}已终止,子进程{os.getpid()}即将退出") return # 执行你的原有业务逻辑 # ... 这里写你原本的长时任务代码 ... # 循环任务可加休眠避免空转;单次任务可将检查放在任务间隙 time.sleep(check_interval)
优点:实现简单,无额外依赖,跨平台可用
缺点:子进程退出有延迟,需等到下一次检查周期才能发现父进程已死
方案2:利用进程组批量终止子进程(Unix/Linux专属)
在Unix/Linux系统中,可通过进程组将父进程和所有子进程绑定,当父进程收到终止信号时,一次性杀掉整个进程组的所有进程。
步骤1:启动子进程时设置进程组
修改你启动子进程的代码,添加preexec_fn参数创建新进程组:
import os from multiprocessing import Process def set_process_group(): # 创建新进程组,让子进程成为进程组首进程 os.setpgrp() def execute(self, process_id): self.__process_id = process_id process_dir = self.__dependencies["process_dir"] self.fit_dependencies() process = Process( target=self.function_wrapper, name=process_id, args=(self.__parameters, self.__config, process_dir,), preexec_fn=set_process_group # Unix系统下生效 ) process.start()
步骤2:父进程监听终止信号,杀掉整个进程组
在Flask应用初始化处,添加信号处理逻辑,当父进程收到SIGTERM(部署重启常用终止信号)或SIGINT(Ctrl+C)时,终止整个进程组:
import signal import os import sys from flask import Flask app = Flask(__name__) def handle_shutdown(signum, frame): try: # 获取当前进程所在的进程组ID pgid = os.getpgid(os.getpid()) # 向整个进程组发送终止信号 os.killpg(pgid, signal.SIGTERM) except OSError: # 避免无子女进程时抛出异常 pass # 正常退出父进程 sys.exit(0) # 注册信号处理函数 signal.signal(signal.SIGTERM, handle_shutdown) signal.signal(signal.SIGINT, handle_shutdown)
优点:子进程能立即被终止,无延迟,适合Unix环境生产场景
缺点:Windows系统不支持进程组,无法使用此方案
方案3:使用专业任务队列(生产环境推荐)
如果你的Web应用有大量长时任务需要管理,推荐使用专门的任务队列框架(比如Celery),将任务从Flask进程中剥离,交给独立的Worker进程处理。这样Flask重启时,Worker进程不受影响,还能实现任务监控、重试、调度等高级功能。
简单示例:
- 安装Celery和消息中间件(比如Redis):
pip install celery redis
- 配置Celery任务:
# celery_tasks.py from celery import Celery # 初始化Celery,用Redis作为消息中间件和结果存储 app = Celery('my_tasks', broker='redis://localhost:6379/0', backend='redis://localhost:6379/0') @app.task def long_running_task(parameters, config, process_dir): # 这里写你的长时任务逻辑 # ...
- 在Flask应用中提交任务:
from celery_tasks import long_running_task def execute(self, process_id): self.__process_id = process_id process_dir = self.__dependencies["process_dir"] self.fit_dependencies() # 异步提交任务到Celery Worker long_running_task.delay(self.__parameters, self.__config, process_dir)
- 启动Celery Worker:
celery -A celery_tasks worker --loglevel=info
优点:任务管理能力强,支持重试、定时任务、监控等,适合复杂生产场景
缺点:需额外部署消息中间件和Worker进程,学习成本稍高
内容的提问来源于stack exchange,提问作者Robin

