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

如何关闭shell=True时subprocess.Popen启动的多bot子进程?

解决Python启动bat后无法彻底关闭子进程的问题

核心原因

你用shell=True启动bat时,subprocess.Popen创建的是cmd.exe进程,而bat里的bot程序是它的子进程。直接调用Popen.kill()只会终止cmd.exe,子进程(bot)会变成孤儿进程继续运行,所以需要针对进程组或子进程PID进行处理。

方案1:创建独立进程组(推荐,无需额外查找子进程)

Windows下可以通过creationflags=subprocess.CREATE_NEW_PROCESS_GROUP创建独立进程组,发送中断信号就能一次性终止整个进程组(包括cmd和bot子进程)。

代码示例

import subprocess
import signal

# 用字典存储每个bot的进程对象,key可以是自定义标识(如bot1、bot2)
bot_processes = {}

# 启动指定bot
def start_bot(bot_id, bat_path, work_dir):
    # 直接调用cmd.exe执行bat,无需shell=True
    process = subprocess.Popen(
        ["cmd.exe", "/c", bat_path],
        cwd=work_dir,
        creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
    )
    bot_processes[bot_id] = process
    print(f"已启动bot {bot_id},PID: {process.pid}")

# 关闭指定bot
def stop_bot(bot_id):
    process = bot_processes.get(bot_id)
    if not process:
        print(f"bot {bot_id}未运行")
        return
    
    # 向进程组发送CTRL_BREAK_EVENT,终止所有子进程
    process.send_signal(signal.CTRL_BREAK_EVENT)
    # 等待进程完全退出
    process.wait()
    del bot_processes[bot_id]
    print(f"已关闭bot {bot_id}")

# 示例调用
start_bot("bot1", "stater.bat", "F:/1/bot/")
# 按需调用stop_bot("bot1")

方案2:查找并终止子进程(兼容shell=True场景)

如果必须保留shell=True,可以先获取cmd.exe的PID,再通过wmic命令查找它的所有子进程,逐个终止后再关闭cmd进程。

代码示例

import subprocess
import re

# 存储每个bot对应的cmd进程PID
bot_processes = {}

# 启动bot(shell=True)
def start_bot(bot_id, bat_cmd, work_dir):
    process = subprocess.Popen(bat_cmd, cwd=work_dir, shell=True)
    bot_processes[bot_id] = process.pid
    print(f"已启动bot {bot_id},cmd PID: {process.pid}")

# 根据父PID获取所有子进程PID
def get_child_pids(parent_pid):
    try:
        # 调用wmic查询子进程
        output = subprocess.check_output(
            ["wmic", "process", "where", f"parentprocessid={parent_pid}", "get", "processid"],
            text=True
        )
        # 提取输出中的数字PID(排除标题行)
        pid_list = re.findall(r"\d+", output)
        return [int(pid) for pid in pid_list[1:]] if len(pid_list) > 1 else []
    except subprocess.CalledProcessError:
        return []

# 关闭指定bot
def stop_bot(bot_id):
    parent_pid = bot_processes.get(bot_id)
    if not parent_pid:
        print(f"bot {bot_id}未运行")
        return
    
    # 先终止所有子进程(bot程序)
    child_pids = get_child_pids(parent_pid)
    for pid in child_pids:
        subprocess.run(["taskkill", "/F", "/PID", str(pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    # 再终止cmd进程
    subprocess.run(["taskkill", "/F", "/PID", str(parent_pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    del bot_processes[bot_id]
    print(f"已关闭bot {bot_id}")

# 示例调用
start_bot("bot1", "stater.bat", "F:/1/bot/")
# 按需调用stop_bot("bot1")

注意事项

  • 优先使用方案1,避免shell=True带来的额外进程层级问题;
  • 方案2中taskkill /F是强制终止,若bot有优雅退出逻辑,可去掉/F换成/T(终止进程树),但需要测试兼容性;
  • 多bot场景下,务必用唯一标识(如bot名称、编号)关联进程信息,确保精准控制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 10:35:26