subprocess 如何在 timeout 时优雅杀死子进程树
技术百科
冰川箭仙
发布时间:2026-01-27
浏览: 次 subprocess.run() 的 timeout 参数只终止主进程,不清理子进程树;应使用 start_new_session=True(Linux/macOS)或 CREATE_NEW_PROCESS_GROUP(Windows)配合 killpg/CTRL_BREAK_EVENT,或用 psutil 显式遍历终止整个进程树。
subprocess.run() 的 timeout 参数只杀主进程,不清理子进程树
Python 的 subprocess.run() 或 subprocess.Popen.wait(timeout=...) 在超时时只会向直接子进程发送信号(如 SIGTERM),但不会递归终止其派生的整个进程树。这意味着 shell 启动的命令链(如 bash -c "sleep 10 & sleep 20")或通过 nohup/& 后台启动的子进程大概率残留。
用 start_new_session=True 配合 os.killpg() 杀整个进程组
关键在于让子进程及其后代运行在独立会话(session)中,这样就能用进程组 ID(PGID)一次性终结整棵树。核心是:start_new_session=True(Linux/macOS)或 creationflags=subprocess.CREATE_NEW_PROCESS_GROUP(Windows)。
- Linux/macOS 下:调用
os.setsid()创建新会话,主进程成为会话首进程,其 PGID = PID;后续 fork 出的子进程默认加入该 PGID - 启动时必须加
start_new_session=True,否则os.getpgid(proc.pid)可能报错或返回父进程组 ID - 超时后用
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)终止整个组,再wait()收尸
import subprocess, os, signal, timeproc = subprocess.Popen( ["bash", "-c", "sleep 5; echo 'done'"], start_newsession=True, # 必须! stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) try: stdout, = proc.communicate(timeout=2) except subprocess.TimeoutExpired: os.killpg(os.getpgid(proc.pid), signal.SIGTERM) proc.wait() # 等待进程组彻底退出
Windows 上要用 CREATE_NEW_PROCESS_GROUP + CTRL_BREAK_EVENT
Windows 没有 POSIX 进程组概念,os.killpg 不可用。必须用 subprocess.CREATE_NEW_PROCESS_GROUP 创建独立进程组,再用 os.kill() 发送 signal.CTRL_BREAK_EVENT(不能用 CTRL_C_EVEN

-
CTRL_BREAK_EVENT能传递给整个控制台进程组,比TerminateProcess更温和 - 必须确保子进程是控制台应用(非 GUI),否则信号无效
- 调用前需先
proc.terminate()或直接os.kill(proc.pid, signal.CTRL_BREAK_EVENT),之后仍要proc.wait()
更健壮的做法:用 psutil 回收所有后代进程
如果无法控制启动参数(比如不能加 start_new_session),或者跨平台兼容性要求高,推荐用 psutil 手动遍历并终止子树。它不依赖会话机制,而是靠 proc.children(recursive=True) 获取全部后代。
- 安装:
pip install psutil - 注意:Windows 上需管理员权限才能终止某些系统相关子进程
- 务必按逆序终止(先叶子后父进程),避免子进程被 init 进程领养而逃逸
- 示例中
proc.children(recursive=True)返回的是实时快照,若进程瞬间启停,可能漏掉极短命子进程
import subprocess, psutil, timeproc = subprocess.Popen(["bash", "-c", "sleep 3 &"]) try: proc.communicate(timeout=1) except subprocess.TimeoutExpired: parent = psutil.Process(proc.pid) children = parent.children(recursive=True) for child in reversed(children): # 先杀孙子,再杀儿子 try: child.terminate() except (psutil.NoSuchProcess, psutil.AccessDenied): pass try: parent.terminate() parent.wait(timeout=3) except (psutil.NoSuchProcess, psutil.TimeoutExpired): pass
实际项目里,start_new_session=True 是最轻量且可靠的选择,但前提是能掌控子进程启动方式;一旦涉及遗留脚本、第三方二进制或容器化环境,psutil 的显式树遍历反而更可控——毕竟进程关系不是靠约定,而是靠实时探测。
# ai
# python
# windows
# mac
# win
# linux
# macos
# cos
# access
# red
# session
# bash
# pip
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Win10如何卸载自带Edge_Win10彻底卸载
- Mac如何彻底清理浏览器缓存?(Safari与Ch
- 电脑的“网络和共享中心”去哪了_Windows 1
- Win11怎么设置多显示器任务栏 Win11扩展任
- 如何使用Golang实现容器自动化运维_Golan
- Win11时间不对怎么同步_Win11自动校准互联
- Win11笔记本怎么看电池健康度_Win11电池报
- php本地部署后session无法保存_sessi
- 如何提升Golang程序I/O性能_Golang
- php怎么操作Redis_Redis扩展连接与基本
- Flask 表单数据通过 SMTP 发送邮件的完整
- Win11如何设置鼠标灵敏度_Win11鼠标灵敏度
- Go 语言标准库为何不提供泛型 Contains
- Win11相机打不开提示错误怎么修_相机权限开启与
- 如何解决同一段404代码在不同主机上表现不一致的问
- Win11无法安装软件怎么办_Win11解除应用安
- 如何使用Golang管理模块版本_Golanggo
- Win11怎么设置默认邮件客户端 Win11修改M
- Windows10如何彻底关闭自动更新_Win10
- php怎么下载安装后无法解析php文件_服务器配置
- Windows 11如何开启文件夹加密(EFS)_
- c# await 一个已经完成的Task会发生什么
- 如何使用Golang反射创建map对象_动态生成键
- Python字符串处理进阶_切片方法解析【指导】
- Win11怎么关闭自动调节亮度 Win11禁用内容
- MAC如何启用访达侧边栏显示_MAC Finder
- Win11怎么查看显卡温度 Win11任务管理器查
- C++友元类使用场景_C++类间协作设计方式讲解
- Win11怎么关闭系统推荐内容_Windows11
- c++ stringstream用法详解_c++字
- Windows笔记本无法进入睡眠模式怎么办?(电源
- Win11怎么禁用键盘自带键盘_Win11笔记本禁
- Windows如何查看和管理已安装的字体?(字体文
- php修改数据怎么批量改状态_批量更新status
- Win11怎么更改盘符_Win11磁盘管理修改驱动
- 如何在JavaScript中动态拼接PHP的bas
- PHP接收参数值为空怎么办_判断和处理空参数方法说
- Win11怎么关闭资讯和兴趣_Windows11任
- php命令行怎么运行_通过CLI模式执行PHP脚本
- PHP cURL GET请求:正确设置认证与自定义
- php增删改查需要哪些扩展_开启mysqli或pd
- php能跑在stm32上吗_php在stm32微控
- 如何诊断并终止卡死的 multiprocessin
- Win11怎么设置闹钟_Windows 11时钟应
- php错误怎么开启_display_errors与
- 如何在 Go 中调用动态链接库(.so)中的函数
- Win11应用商店下载慢怎么办 Win11更改DN
- Win11怎么更改默认打开方式_Win11关联文件
- Win11怎么设置单手模式_Win11触控键盘布局
- phpstudy本地环境mysql忘记密码_重置m

QQ客服