Python multiprocessing.Queue 关闭后为什么还有后台线程
先说结论:multiprocessing.Queue.close() 关闭的是队列的继续使用入口,不是把后台线程强行杀掉。只要队列里还有尚未写入管道的数据,负责搬运缓冲数据的 feeder thread 就会继续工作;想等待它真正退出,要在 close() 之后调用 join_thread()。
这也是 Python 多进程程序里“明明 close 了,进程退出还要等一会儿”的主要原因。正确的判断标准不是线程是否立刻消失,而是生产者已经停止 put()、消费者能够取完数据,并且退出前完成必要的 flush。
- 第一次
put()可能启动 feeder thread,它把已序列化的数据从内存缓冲区写入 pipe。 close()表示不再使用队列;join_thread()才是等待后台线程把缓冲数据刷完。- 子进程写队列时,先让消费者排空,再
join()子进程;不要用cancel_join_thread()代替正常收尾。
multiprocessing.Queue 的后台线程到底在忙什么
multiprocessing.Queue 不是一个“每次 put() 都同步写完 pipe”的简单容器。进程第一次向空队列写入对象后,Queue 会准备一个本地缓冲区,并启动 feeder thread。调用线程很快返回,feeder thread 再把对象序列化后的内容送到跨进程 pipe。
因此,put() 返回只说明对象已经交给 Queue 的本地缓冲路径,不等于另一端已经读取。close() 也只是告诉 Queue:后面不应再调用 put()、get() 或 empty()。只要缓冲区里还有数据,feeder thread 就必须继续刷写;刷完后它才会退出。

这也解释了为什么刚执行完 close() 时观察到线程仍在运行并不异常。它可能只是在处理最后一批序列化数据。若代码需要在当前进程继续执行且明确等到队列发送完,可以使用下面的收尾组合:
| 方法 | 作用 | 使用边界 |
|---|---|---|
put() | 把对象交给 Queue | 返回不代表 pipe 已经写完 |
close() | 释放队列内部资源并停止继续使用 | 后面不要再 put/get |
join_thread() | 等待 feeder thread 退出 | 必须先 close,适合保证数据刷完 |
cancel_join_thread() | 不自动等待 feeder thread | 可能丢数据,只用于明确接受丢失的场景 |
正确关闭顺序:先停止写入,再等待发送完成
单进程生产者最容易写成“写完就 close,结束就算了”。更清晰的做法是把关闭动作写在生产者职责里:不再 put() 后先 close(),再 join_thread()。消费者则持续读取,最后再关闭自己不再使用的 Queue 引用。
import multiprocessing as mp
def producer(queue):
# put() 只把对象交给 Queue 的本地缓冲路径
queue.put({"job": "index", "count": 3})
# 明确停止写入,再等待 feeder thread 刷完 pipe
queue.close()
queue.join_thread()
if __name__ == "__main__":
queue = mp.Queue()
process = mp.Process(target=producer, args=(queue,))
process.start()
# 先消费生产者写入的数据,避免生产者的大对象长期堵在 pipe 上
print(queue.get())
process.join()
# 父进程自己的 Queue 引用也不再使用
queue.close()
queue.join_thread()
这里的关键不是把两个方法机械地放到每个 Queue 后面,而是明确“谁还会写、谁负责读、谁等待谁”。如果子进程还会 put(),父进程不要在没有消费数据前急着 process.join();数据量大时,子进程可能要等 feeder thread 把缓冲内容写进 pipe,而父进程又在等子进程退出,两个等待就会互相卡住。
为什么 close 之后线程仍在:三种常见误判
第一种误判是把 close() 当成“清空并销毁”。它不会丢弃已经交给 Queue 的缓冲数据,而是给 feeder thread 一个完成收尾的机会。第二种误判是用 empty() 或 qsize() 判断是否已经刷完;这些值受并发时序影响,只适合非常有限的观测,不是可靠的完成信号。
第三种误判是遇到退出慢就直接调用 cancel_join_thread()。它的语义更接近“允许当前进程不等剩余数据”,不是“更快地安全关闭”。如果队列里的内容不能丢,应该保留正常的读取、close()、join_thread() 顺序。

还要注意 multiprocessing.Queue 与 multiprocessing.SimpleQueue 不是同一个实现。SimpleQueue 更接近带锁的 Pipe,没有 Queue 这套 feeder thread 收尾接口;如果业务只需要简单的双向交接,可以重新评估是否真的需要 Queue 的缓冲和容量控制。
排查清单:进程退出慢或 join 卡住时先看这几项
- 搜索所有
put()调用,确认生产者执行close()后不会再写入。 - 确认消费者先持续
get(),再等待写入进程join(),尤其是单条消息可能很大的场景。 - 需要保证数据落到 pipe 时,在同一个写入进程中使用
close()加join_thread()。 - 不要把
empty()、qsize()或“线程暂时不可见”当作可靠完成信号。 - 只有在明确允许丢弃未刷数据、且进程必须立即退出时,才考虑
cancel_join_thread()。
常见问题
close() 之后还能继续 get() 吗?
不能。Queue 关闭后不应再调用 get()、put() 或 empty()。需要继续消费时,应先完成消费,再关闭读取方持有的 Queue 引用。
join_thread() 为什么必须放在 close() 后面?
因为它等待的是已经进入收尾状态的 feeder thread。先 close() 才能明确告诉 Queue 不会再有新的写入;否则就无法定义“什么时候算刷完”。
Queue 里的数据一定按多个进程的 put 顺序到达吗?
同一个进程连续写入的相对顺序会保留,但多个进程并发写入时,跨进程的整体到达顺序不应作为业务协议。需要严格顺序时,应在消息中携带序号并由消费者重排。
Go os.ReadFile 读取目录时返回错误怎么解释
- 上一篇
- Go os.ReadFile 读取目录时返回错误怎么解释
- 下一篇
- Go crypto/hmac 怎么比较签名避免时序差异
-
- 文章 · python教程 | 1小时前 | JSON · Python教程 · 异常排查 · 数据解析 · Python json.loads JSONDecodeError lineno colno pos JSON排错
- Python JSONDecodeError 的 lineno 和 colno 怎么映射到原文
- 326浏览 收藏
-
- 文章 · python教程 | 3小时前 | python · CSV数据处理 · Python csv csv.DictReader 重复列名
- Python csv.DictReader 遇到重复列名时怎么保存数据
- 371浏览 收藏
-
- 文章 · python教程 | 4小时前 |
- Python sqlite3 事务提交后游标还能不能继续使用
- 181浏览 收藏
-
- 文章 · python教程 | 7小时前 | 缓存 · python · dataclasses · dataclass default_factory frozen=True
- Python dataclass frozen=True 里怎么保存可变缓存
- 256浏览 收藏
-
- 文章 · python教程 | 8小时前 |
- Python pathlib.glob 怎么排除隐藏目录并保持递归
- 142浏览 收藏
-
- 文章 · python教程 | 12小时前 | python · logging · 结构化日志 · Python logging LoggerAdapter
- Python logging 多模块共享上下文时怎么用 LoggerAdapter
- 215浏览 收藏
-
- 文章 · python教程 | 13小时前 |
- Python frozen dataclass 里怎么保存派生字段
- 213浏览 收藏
-
- 文章 · python教程 | 15小时前 |
- Python dataclass 如何用 InitVar 接收临时构造参数
- 264浏览 收藏
-
- 文章 · python教程 | 16小时前 |
- Python sqlite3 多线程共享连接时报错怎么改
- 264浏览 收藏
-
- 文章 · python教程 | 17小时前 |
- Python sqlite3 with 代码块退出后为什么没有提交数据
- 488浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 31次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 187次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 121次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 46次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 30次使用
-
- go格式“占位符”输入输出 类似python的input
- 2023-01-19 346浏览
-
- Golang如何调用Python代码详解
- 2023-01-07 235浏览
-
- Golang中数据结构Queue的实现方法详解
- 2023-01-07 323浏览
-
- HTTP 的 response 中的响应体和头部是分开发送的吗?
- 2023-01-28 387浏览
-
- B站等视频网站的弹幕用的是 websocket 还是轮询?
- 2023-02-16 447浏览

