如何关闭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
相关产品推荐
相关产品推荐

