当前位置: 首页 > news >正文

Python 中使用多进程编程的“三两”问题

文章目录

  • 一、简介
  • 二、选择合适的启动方式
  • 三、手动终止所有的进程
  • 小结

一、简介

这里简单介绍在Python中使用多进程编程的时候容易遇到的情况和解决办法,有助于排查和规避某类问题,但是具体问题还是需要具体分析,后续会补充更多的内容。

二、选择合适的启动方式

  1. Python多进程编程非常需要注意启动方式,有些库会在fork模式下出现很诡异的阻塞或显存泄漏。如果需要跨平台使用,优先选择spawn方式,如果涉及到 GPU、PyTorch、OpenCV 视频流、TensorRT、OpenVINO ,同样优先选择spawn方式,避免继承GPU上下文。有的时候会存在 多线程多进程 混用的情况,可以选择spawnforkserver这两种方式。 spawn启动方式通常更安全、更稳定。

    启动方式平台默认机制优点缺点适用场景
    forkLinux / macOS子进程直接复制父进程的内存空间(写时复制),从 fork() 返回处继续执行创建速度快,占用内存少继承父进程线程状态可能死锁,继承 GPU/文件句柄可能出错,不支持 Windows纯计算、多进程间状态共享简单、无 GPU/大文件句柄的场景
    spawnWindows启动全新的 Python 解释器进程,从头执行 __main__ 模块的代码跨平台一致,状态干净,不继承父进程线程/句柄/GPU context启动慢,初始化开销大,需要 if __name__ == "__main__":GPU 深度学习、跨平台项目、多线程+多进程混合、需要隔离状态的场景
    forkserverLinux / macOS先启动一个 “fork server” 进程,之后所有子进程由它 fork 出来避免从主进程直接 fork(减少死锁风险),比 spawn 快只在类 Unix 系统可用,需额外启动 server,复杂度高多线程+多进程并存、需要避免主进程 fork 的高并发服务
  2. 可以通过调用multiprocessing.set_start_method('spawn')来指定子进程的启动方式,通常写在if __name__ == "__main__":里,force=True参数可以强制重置启动方式(但通常不建议随便用,除非确定没别的进程创建了子进程)。

    import multiprocessing as mpdef worker(name):print(f"Worker {name} running...")if __name__ == "__main__":# 强制统一 spawnmp.set_start_method("spawn", force=True)  # "fork" 或 "forkserver" processes = []for i in range(3):p = mp.Process(target=worker, args=(i,))p.start()processes.append(p)for p in processes:p.join()
    
  3. 使用 PyAV 调用h264_qsv(Intel Quick Sync Video)硬件加速解码时,也会受多进程启动方式的影响,QSV 是基于英特尔硬件加速的编码解码技术,需要在进程中正确初始化硬件上下文。如果使用fork,子进程会复制父进程的内存空间,但硬件设备上下文(如 QSV 的硬件句柄、驱动状态等)通常不能被正确继承或共享,导致解码失败、死锁或崩溃。使用spawn,子进程是全新启动,独立初始化硬件上下文,能避免因上下文继承带来的问题,提高稳定性。

    多进程启动方式对 PyAV + h264_qsv 硬解码的影响推荐做法
    fork可能导致硬件上下文冲突,解码异常不推荐
    spawn子进程独立初始化硬件上下文,稳定性高推荐,尤其多进程并发的时候
  4. 在使用fork启动多进程的时候,部分常用的 计算机视觉 (CV)和 点云处理 相关库可能会出现问题,任何涉及 GPU上下文、线程池或多线程加速的库,在fork多进程启动方式下都可能出现资源继承异常、死锁、崩溃等问题。点云库中,Open3DPCL 较为复杂,fork时要特别注意。

    库/模块说明与常见问题
    OpenCV (cv2)GPU模式(CUDA)在fork后可能资源异常或崩溃,多线程环境下死锁;CPU模式一般安全,但多线程仍需注意。
    Open3D内部使用线程池和GPU(部分功能),fork启动时可能导致线程池状态异常或GPU资源不可用。
    PCL / python-pcl底层多线程和GPU支持(部分平台),fork后可能出现线程死锁或资源冲突,尤其在GPU加速时。
    PyTorchfork后CUDA上下文继承出错,导致报错、卡死;多线程资源也可能异常。
    TensorFlowfork后session、资源无法正常初始化,多线程管理导致崩溃。
    OpenVINO线程池和设备初始化受fork影响,可能导致设备资源冲突或初始化失败。
    TensorRTGPU上下文和优化缓存fork后不稳定,可能导致初始化失败或运行错误。
    matplotlibGUI线程在fork后可能卡死或异常。

三、手动终止所有的进程

  1. 多进程中 Ctrl+CSIGINT 信号)后程序没有全部退出,最常见原因就是信号没有传递给子进程,或者子进程没能响应退出请求。fork导致资源继承异常也经常是根源。列出当前系统中所有正在运行的 Python 进程及其详细信息。

    ps aux | grep python
    
  2. 在多进程场景下,torch.utils.data.DataLoader是常见的导致进程无法退出的元凶之一,尤其是在num_workers > 0时。有的情况默认不监听结束信号,有的情况收不到结束信号。显式处理可能比较麻烦。

  3. 一个比较安全的写法是在主进程捕获SIGINT,然后主动杀掉自己启动的所有子进程。直接一次性结束整个进程树。

    import multiprocessing as mp
    import signal
    import sys
    import os
    import psutildef kill_all_children(timeout=3):"""终止当前进程的所有子进程(递归),先尝试终止,再强制杀死超时未结束的子进程"""proc = psutil.Process(os.getpid())children = proc.children(recursive=True)# 尝试终止for child in children:try:child.terminate()except Exception as e:print(f"[警告] 无法终止 PID={child.pid}: {e}", file=sys.stderr)# 等待超时,获取仍然存活的子进程gone, alive = psutil.wait_procs(children, timeout=timeout)# 强制杀死仍然存活的进程for child in alive:try:child.kill()except Exception as e:print(f"[错误] 无法强制终止 PID={child.pid}: {e}", file=sys.stderr)def signal_handler(sig, frame):   # 回调函数print(f"\n[退出] 捕获信号 {sig},终止子进程并退出...")try:kill_all_children()except Exception as e:print(f"[错误] 终止子进程时出错: {e}", file=sys.stderr)finally:sys.exit(130 if sig == signal.SIGINT else 143)  # 130 = Ctrl+C 退出, 143 = SIGTERM 退出def main(args):# 启动多个进程的示例processes = []for _ in range(4):p = mp.Process(target=worker_function)p.start()processes.append(p)for p in processes:p.join()if __name__ == "__main__":signal.signal(signal.SIGINT, signal_handler)   # 注册信号和回调函数signal.signal(signal.SIGTERM, signal_handler)mp.set_start_method('spawn', force=True)args = parse_args()main(args)
    
  4. 或者更简单直接的立即杀掉所有进程,终端更干净。可以根据实际情况考虑如何使用。

    import multiprocessing as mp
    import signal
    import sys
    import os
    import psutildef kill_all_processes():proc = psutil.Process(os.getpid())for child in proc.children(recursive=True):child.kill()proc.kill()def signal_handler(sig, frame):print("\n[退出] 捕获 Ctrl+C,终止所有进程...")kill_all_processes()sys.exit(0)def main(args):# 启动多个进程的示例processes = []for _ in range(4):p = mp.Process(target=worker_function)p.start()processes.append(p)for p in processes:p.join()if __name__ == "__main__":signal.signal(signal.SIGINT, signal_handler)signal.signal(signal.SIGTERM, signal_handler)mp.set_start_method('spawn', force=True)args = parse_args()main(args)
    

小结

以上内容来自相关资料和个人实践,具体情况还请具体分析,可以参考这里提到的几个关键点,有助于排查问题,如有问题欢迎在评论区指正,谢谢!!

http://www.dtcms.com/a/328907.html

相关文章:

  • 如何记录日常笔记?
  • AAT Bioquest 细胞凋亡检测方法集锦
  • 数组和矩阵的核心关系及关键区别
  • C# xml UI格式化字符串
  • Java -- Vector底层结构-- ArrayList和LinkedList的比较
  • 河南萌新联赛2025第五场 - 信息工程大学
  • AI-调查研究-50-大数据调研报告 二十年演进:从Hadoop批处理到Flink实时计算的架构变革
  • OpenCV的实际应用
  • 121-基于FLask的共享单车需求数据可视化分析系统
  • ACWing 算法基础课-数据结构笔记
  • 闹钟时间到震动与声响提醒的实现-库函数版(STC8)
  • Android平台RTSP播放器选型指南:从开源方案到跨平台低延迟专业SDK
  • Flink DataStream 按分钟或日期统计数据量
  • 资源查看-lspci命令
  • django request.data.get 的值修改
  • python二叉树的深度优先遍历和广度优先遍历
  • OpenAI官方写的GPT-5 prompt指南
  • Prompt工程师基础技术学习指南:从入门到实战
  • 实战多屏Wallpaper壁纸显示及出现黑屏问题bug分析-学员作业
  • 理解RESTful架构:构建优雅高效的Web服务
  • 直播美颜SDK开发实战:高性能人脸美型的架构与实现
  • STM32HAL 快速入门(六):GPIO 输入之按键控制 LED
  • 代码架构之 BO、PO、DTO
  • 边缘计算:数据处理新范式的革命性架构
  • Dots.ocr:告别复杂多模块架构,1.7B参数单一模型统一处理所有OCR任务22
  • 系统垃圾清理批处理脚本 (BAT)
  • 电子电气架构 --- 软件项目文档管理
  • OpenCVSharp中的HDR成像
  • 杂记 01
  • electron进程间通信- 从渲染进程到主进程