当前位置:首页 > 文章列表 > 文章 > python教程 > Python 多进程 Queue 传输结果的关闭方式

Python 多进程 Queue 传输结果的关闭方式

来源:17golang原创 2026-10-17 08:04:02 0浏览 收藏

Python 的 multiprocessing.Queue 适合在多个进程之间传递结果,但它不是一个“调用 close() 就立刻结束”的普通列表。对象会先被序列化,生产进程还可能通过后台 feeder thread 把缓冲区里的数据刷入管道。正确的收尾顺序通常是:主进程先持续 get(),确认生产端都发出结束标记后再 join() 子进程,最后关闭主进程自己的队列句柄。

官方地址:https://docs.python.org/3/library/multiprocessing.html

下面用一个“多个 worker 返回结果”的小场景说明这套顺序。重点不是把所有进程 API 罗列一遍,而是解决结果已经放进 Queue 后,程序退出时为什么还会卡住的问题。

Queue 关闭前要先理解 feeder thread

multiprocessing.Queue.put() 并不等于调用进程已经把完整对象同步写入管道。Queue 会对对象做序列化,并在需要时启动后台 feeder thread,将进程内的缓冲内容继续送往管道。消费进程通过 get() 读取的是已经进入队列传输通道的数据。

Python multiprocessing Queue 从生产进程经序列化、缓冲区和 feeder thread 到消费进程的结构说明图
图1:multiprocessing.Queue 的内部关系说明图,展示结果从生产进程到消费进程的传输边界。

因此,close() 的含义是释放当前进程持有的 Queue 内部资源,并且这个进程不能再对它调用 put() 或 get()。它不是向其它进程广播“所有数据都已被读取”的信号,也不替代进程间的结束协议。

先约定结果和结束标记

多个 worker 时,主进程不能只依赖 empty() 判断任务是否结束。队列可能正在接收数据,empty() 也不适合作为可靠的跨进程完成条件。更稳妥的方式是让每个 worker 在结果之后放入一个 None sentinel,主进程收到与 worker 数量相同的 sentinel 后,才认为生产端全部结束。

这个约定把“业务结果”和“生产者结束”区分开:普通字典是业务数据,None 只负责结束计数。若业务本身允许返回 None,就改用一个明确的字典标记或专用对象。

一套可复用的安全收尾代码

下面的例子使用 spawn 上下文,显式展示跨平台进程入口。worker 完成最后一次 put() 后关闭自己持有的队列句柄,并等待自己的 feeder thread 刷完;主进程则一边消费,一边等待所有 sentinel。

import multiprocessing as mp


def worker(result_queue, worker_id):
    try:
        # 结果对象会被序列化后交给 Queue 的传输缓冲区。
        result_queue.put({"worker": worker_id, "value": worker_id * 10})
        # sentinel 只表示这个生产者不再提交新的结果。
        result_queue.put(None)
    finally:
        # 当前 worker 不再使用自己的 Queue 句柄,先释放内部资源。
        result_queue.close()
        # 等待本进程 feeder thread 把已入队数据刷入管道。
        result_queue.join_thread()


if __name__ == "__main__":
    # spawn 要求进程创建代码放在保护块中。
    context = mp.get_context("spawn")
    result_queue = context.Queue()
    processes = [
        context.Process(target=worker, args=(result_queue, worker_id))
        for worker_id in range(3)
    ]

    for process in processes:
        # 先启动生产者,再由主进程持续取结果。
        process.start()

    results = []
    finished_workers = 0
    while finished_workers  join_thread 顺序清理。
    result_queue.close()
    result_queue.join_thread()
    print(sorted(results, key=lambda item: item["worker"]))

这段代码的关键点是主进程没有在 process.join() 前停止读取 Queue。对于单个小结果,错误顺序可能暂时看不出问题;当结果较大或数量较多时,子进程可能还在等待 feeder thread 刷空缓冲区,主进程却先等待子进程结束,于是形成互相等待。

Python 多进程 Queue 消费结果、等待进程和关闭队列之间依赖关系的结构图
图2:多进程 Queue 收尾依赖结构图,强调先消费队列再等待生产进程结束。

close、join_thread 和 process.join 分别负责什么

可以把三个方法分成三种对象来理解:

方法作用对象正确使用时机
Queue.close()当前进程的 Queue 句柄当前进程不再 put/get 时调用,之后不要继续使用该 Queue
Queue.join_thread()当前进程的 feeder thread先 close,再等待缓冲数据刷入管道
Process.join()子进程本身主进程已经消费完所需结果或结束标记后调用

生产端和消费端各自持有 Queue 的进程内句柄,所以 worker 的 close() 不会让主进程的 get() 失效。主进程必须等自己的消费工作完成后再关闭自己的句柄。若只有一个生产者,也可以用一个 sentinel;如果任务数量本身固定,也可以按固定结果数读取,但仍要保证所有生产者的进程回收顺序不会堵住未消费的数据。

最容易踩中的两种关闭错误

错误一:先 join 子进程,再读取 Queue

# 可能造成阻塞:子进程的缓冲数据还没有被消费。
process.join()
item = result_queue.get()

当子进程向 Queue 放入的数据较大时,子进程退出前需要 feeder thread 把缓冲内容送入管道。主进程先 join(),就没有机会继续 get() 腾出传输空间。修复方式是把 get() 放在前面,或者使用独立的协调协议,确保消费者不会停读。

错误二:close 后继续 put 或 get

# close 表示当前进程不再使用这个 Queue。
result_queue.close()
# 这里继续 put/get 属于错误的生命周期使用。
result_queue.put({"late": True})

如果业务还需要发送数据,说明关闭时机太早,应把 close() 放到最后一次 put() 之后。关闭后是否还有其它进程在工作,也不改变当前进程不能继续使用该句柄的规则。

cancel_join_thread 什么时候可以用

cancel_join_thread() 会阻止进程退出时自动等待 Queue 的后台线程。它适合明确接受“进程可以马上退出,未刷完的数据可能丢失”的场景,例如放弃一批无关紧要的临时统计。它不是加速版的 join_thread(),也不是常规生产代码的默认清理方式。

# 只有明确允许丢弃尚未刷出的临时数据时才这样做。
result_queue.cancel_join_thread()
result_queue.close()

正式结果、任务状态和需要审计的消息都不应依赖这个方法保证退出速度。若目标是可靠传输,应让消费者读完、生产端关闭并等待 feeder thread,再回收进程。

排查 Queue 关闭问题的顺序

  1. 确认每个 worker 是否都在最后一次结果后发送了结束标记。
  2. 确认主进程是否持续 get(),而不是先对生产者调用 join()。
  3. 确认 worker 的 close() 和 join_thread() 位于最后一次 put() 之后。
  4. 确认主进程收到所有 sentinel 后才回收进程,并最后清理自己的 Queue。
  5. 只有在明确允许数据丢失时,才考虑 cancel_join_thread()。

记住一句话:Queue 的“关闭”是某个进程释放自己的使用权,进程的“join”是等待子进程结束,二者都不能代替消费协议。把结束标记、消费循环和清理顺序写清楚,多进程传输结果就不容易在退出阶段变成一个隐蔽的死锁。

相关问题

Queue.close() 会通知其它进程停止吗?

不会。它只关闭调用者当前持有的 Queue 资源;跨进程停止需要 sentinel、事件或其它明确协议。

为什么 Queue.put() 成功了,进程还是退出很慢?

因为 put 后的数据可能仍在 feeder thread 的缓冲区中,进程退出时需要等待刷入管道。主进程应继续消费,生产端再按 close 和 join_thread 收尾。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Python statistics.quantiles 计算 P95 时的 n 参数与方法选择Python statistics.quantiles 计算 P95 时的 n 参数与方法选择
上一篇
Python statistics.quantiles 计算 P95 时的 n 参数与方法选择
Python 正则输入规模与超时防护的边界
下一篇
Python 正则输入规模与超时防护的边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    500次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    558次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    586次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    537次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    349次使用