
本文介绍一种简洁稳健的方案,使用 subprocess 直接管理两个外部可执行文件的生命周期,并通过 multiprocessing.event 实现进程间协调,避免序列化错误与资源竞争,确保 process1.exe 在 process2.exe 退出时被及时终止。
本文介绍一种简洁稳健的方案,使用 subprocess 直接管理两个外部可执行文件的生命周期,并通过 multiprocessing.event 实现进程间协调,避免序列化错误与资源竞争,确保 process1.exe 在 process2.exe 退出时被及时终止。
在 Python 脚本中并发控制多个外部 .exe 进程(如 process1.exe 和 process2.exe)时,关键挑战在于:进程生命周期的强耦合性——process2.exe 的异常退出必须立即触发 process1.exe 的终止,且所有后续逻辑(如状态检查)应同步中止。原始代码试图用 multiprocessing.Process 封装 psutil 进程探测逻辑,却因传递不可序列化对象(如 psutil.Process 实例、weakref 等)导致 TypeError: cannot pickle 'weakref.ReferenceType' object 错误——这是 Windows 上 spawn 启动方式对跨进程对象序列化的硬性限制。
根本解法是避免在子进程中传递复杂对象,转而采用更轻量、更可控的协作模型:
✅ 推荐方案:主进程直接管控,事件驱动协同
核心思想:由主进程启动并持有两个 subprocess.Popen 实例,利用 process2.wait() 阻塞等待其结束(天然支持异常退出),再主动终止 process1;同时将耗时检查逻辑(checkStatus)置于独立子进程中,并通过 Event 通知其提前退出。
以下是优化后的完整实现:
import subprocess
import multiprocessing
import time
def checkStatus(event, queue):
# 示例:模拟耗时检查逻辑(如轮询日志、验证端口、读取共享内存等)
for i in range(10):
if event.is_set(): # 主进程已发出终止信号
print("checkStatus: 接收到终止信号,提前退出")
break
print(f"checkStatus: 执行第 {i+1} 次检查...")
time.sleep(1)
else:
print("checkStatus: 检查完成")
queue.put("checkStatus Completed!")
# 注意:此处不再调用 event.wait(),避免死锁 —— 终止信号由主进程单向发出
def main():
# 启动两个进程(注意路径和权限)
process1 = subprocess.Popen(["./process1.exe"])
process2 = subprocess.Popen(
["./process2.exe"],
creationflags=subprocess.CREATE_NEW_CONSOLE
)
# 创建通信机制
queue = multiprocessing.Queue()
event = multiprocessing.Event()
# 启动检查子进程
p_check = multiprocessing.Process(target=checkStatus, args=(event, queue))
p_check.start()
try:
# 关键:阻塞等待 process2 结束(无论正常退出或崩溃)
process2.wait()
print("process2.exe 已退出,准备终止 process1.exe...")
# 立即终止 process1
process1.terminate()
try:
process1.wait(timeout=5) # 等待优雅退出
except subprocess.TimeoutExpired:
process1.kill() # 强制终止
print("process1.exe 未响应,已强制杀死")
# 通知检查进程终止
event.set()
# 等待检查进程结束
p_check.join(timeout=2)
if p_check.is_alive():
p_check.terminate()
p_check.join()
finally:
# 清理队列输出
while not queue.empty():
print(queue.get_nowait())
if __name__ == "__main__":
main()
⚠️ 关键注意事项
- 路径与可执行性:确保 ./process1.exe 和 ./process2.exe 路径正确,且当前用户有执行权限;生产环境建议使用绝对路径或 pathlib.Path.resolve()。
- Windows 控制台行为:creationflags=subprocess.CREATE_NEW_CONSOLE 仅适用于 Windows;Linux/macOS 应移除该参数或改用 start_new_session=True。
- process2.wait() 的健壮性:该调用会立即返回 process2 退出码,即使它意外崩溃(如 segmentation fault),因此无需额外 psutil 监控。
- 避免 psutil.pid_exists() 跨进程传递:原方案中将 PID 字典传入子进程,但 psutil.Process 对象本身不可序列化;新方案完全规避此问题,PID 由主进程本地持有。
- PyInstaller 兼容性:若打包为 .exe,请确保 subprocess 调用的 .exe 文件与主程序在同一目录,或显式指定完整路径;os.startfile() 在 PyInstaller 中可能引发多实例问题,subprocess.Popen 是更可靠的选择。
该方案以最小依赖、最简逻辑达成目标:process2 的生命周期完全主导整个流程,主进程成为协调中枢,子进程仅承担单一职责,彻底规避序列化陷阱与竞态条件。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











