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

使用subprocess.run时如何确保超时子进程被杀死?

问题描述

我用以下Python代码启动子进程:

# Run the program
subprocess_result = subprocess.run(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                check=False,
                timeout=timeout,
                cwd=directory,
                env=env,
                preexec_fn=set_memory_limits,
            )

启动的子进程是带shebang的Python程序,会执行大量计算并写入文件,没有任何信号处理逻辑,可能会超出指定的timeout时长。

根据subprocess.run官方文档,超时后子进程会被杀死:

The timeout argument is passed to Popen.communicate(). If the timeout
expires, the child process will be killed and waited for. The
TimeoutExpired exception will be re-raised after the child process has
terminated.

但实际情况是,超时后我总会收到subprocess.TimeoutExpired异常,但偶尔子进程没被杀死,还在消耗机器资源。请问我操作有误吗?如果有误问题出在哪?如果操作没问题,这个问题的原因是什么,该怎么解决?

注:我使用Ubuntu 22.04系统与Python 3.10版本。


问题分析与解决方案

你的操作本身没有错误,出现这种情况主要有以下几个原因及对应解决办法:

可能原因

  1. 中间shell进程导致子进程脱离控制
    带shebang的脚本会触发/bin/sh来执行,此时实际的Python计算进程是sh的子进程。默认情况下subprocess.run超时后只向直接启动的sh进程发送终止信号,若sh退出后,其子进程(Python进程)会被init进程接管,继续运行。

  2. preexec_fn的潜在干扰
    你使用的set_memory_limits函数如果修改了进程组设置或信号相关逻辑,可能会干扰subprocess的超时清理流程,导致子进程脱离父进程的控制范围。

解决办法

1. 统一管控进程组

启动子进程时通过start_new_session=True创建新会话,或在preexec_fn中显式设置进程组,确保超时后信号能覆盖整个进程组的所有进程:

import os

subprocess_result = subprocess.run(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                check=False,
                timeout=timeout,
                cwd=directory,
                env=env,
                preexec_fn=lambda: (set_memory_limits(), os.setpgrp()),
                start_new_session=True
            )

2. 跳过shell直接启动Python进程

如果可以,把cmd从指向脚本的路径改为直接调用Python解释器加脚本路径,避免中间shell进程的产生:

# 把cmd从 ["/path/to/script.py"] 改成
cmd = ["python3", "/path/to/script.py"]
subprocess_result = subprocess.run(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                check=False,
                timeout=timeout,
                cwd=directory,
                env=env,
                preexec_fn=set_memory_limits,
            )

3. 捕获异常后手动清理进程

捕获TimeoutExpired异常后,主动检查并杀死主进程及其所有子进程,可借助psutil库实现彻底清理:

import signal
import psutil

try:
    subprocess_result = subprocess.run(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        check=False,
        timeout=timeout,
        cwd=directory,
        env=env,
        preexec_fn=set_memory_limits,
    )
except subprocess.TimeoutExpired as e:
    # 尝试杀死主进程
    if e.poll() is None:
        try:
            os.kill(e.pid, signal.SIGKILL)
            os.waitpid(e.pid, 0)
        except OSError:
            pass
    # 清理所有子进程
    try:
        parent_proc = psutil.Process(e.pid)
        for child in parent_proc.children(recursive=True):
            child.kill()
        parent_proc.kill()
    except (psutil.NoSuchProcess, ImportError):
        pass
    raise

(使用前需安装psutil:pip install psutil)

4. 检查set_memory_limits函数

确认该函数没有执行阻止信号传递的操作,比如修改不可捕获的SIGKILL逻辑,或设置资源限制导致进程进入不可中断的IO阻塞状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 08:05:34