python 中常见的 queue 有两种:
import queue q = queue.queue()
from multiprocessing import queue q = queue()
它们都提供了 put()、get() 等方法,但解决的问题不同。
1.queue.queue
queue.queue 用于同一个进程中的多个线程。
线程共享所属进程的内存,所以它们能够直接操作同一个队列。queue.queue 内部会加锁,避免多个线程同时操作队列时发生数据混乱。
一个进程
├── 线程 a ─┐
├── 线程 b ─┼──> 同一个 queue.queue
└── 线程 c ─┘
示例:
import queue
import threading
q = queue.queue()
def worker():
q.put("线程写入的数据")
thread = threading.thread(target=worker)
thread.start()
thread.join()
print(q.get())
简单来说,线程就像同一个房间里的人,queue.queue 就像房间里的一个公共盒子。
所有人都能直接操作这个盒子,而队列内部的锁可以避免大家同时操作时发生混乱。
2.multiprocessing.queue
multiprocessing.queue 用于同一台机器上的多个进程。
不同进程拥有各自独立的内存,因此不能直接操作同一个普通 python 队列。
父进程的内存 子进程的内存
queue.queue a queue.queue a 的副本
即使创建子进程时把普通的 queue.queue 传进去,子进程操作的也只是自己内存中的队列,父进程无法看到它后续写入的数据。
multiprocessing.queue 通过操作系统管道在进程之间传输数据:
进程 a
│
│ 序列化
▼
操作系统管道
│
│ 反序列化
▼
进程 b
示例:
from multiprocessing import process, queue
def worker(q):
q.put("子进程写入的数据")
if __name__ == "__main__":
q = queue()
process = process(target=worker, args=(q,))
process.start()
print(q.get())
process.join()
可以把多个进程想象成住在不同房间里的人。
他们不能直接操作同一个盒子,所以需要一条传送带,把东西从一个房间送到另一个房间。
multiprocessing.queue 就是这条传送带。
3. 两种 queue 的核心区别
queue.queue
解决的是:多个人在同一个房间里,如何安全地操作同一个盒子。
它依靠共享内存保存数据,并通过线程锁保证操作安全。
一个进程
├── 线程 a ─┐
├── 线程 b ─┼──> queue.queue
└── 线程 c ─┘
multiprocessing.queue
解决的是:人在不同房间里,如何把东西从一个房间送到另一个房间。
它通过序列化、操作系统管道等方式在不同进程之间传递数据。
进程 a ──> 操作系统管道 ──> 进程 b
4. 为什么分布式 manager 使用queue.queue?
在使用 basemanager 实现分布式通信时,真正的队列和数据都保存在 manager 服务端进程里。
其他机器并不会直接操作这个队列,而是通过网络告诉 manager:
请帮我执行
put()。
或者:
请帮我执行
get()。
例如客户端执行:
q.put("任务 a")
看起来像是在直接操作队列,实际上执行过程是:
客户端
│
│ 网络请求:请执行 put("任务 a")
▼
manager 服务端
│
▼
服务端执行 queue.queue.put("任务 a")
│
▼
数据保存在服务端进程的内存中
客户端拿到的 q 不是真正的 queue.queue,而是一个代理对象。
这个代理对象负责把客户端的方法调用转换成网络请求。
客户端代理对象
│
│ 网络通信
▼
manager 服务端
│
▼
真正的 queue.queue
因此,在这种分布式架构中:
- manager 负责跨进程、跨机器通信;
queue.queue负责在服务端进程中保存数据;- 客户端只负责发送操作命令;
- 真正的
put()和get()都在服务端执行; - 数据始终保存在服务端进程中。
5. 多个客户端访问时会发生什么?
多个客户端可以同时向 manager 发送请求:
客户端 a ─┐
客户端 b ─┼── 网络请求 ──> manager 服务端 ──> queue.queue
客户端 c ─┘
manager 服务端可能使用多个线程处理这些请求:
客户端 a ──> manager 服务线程 a ─┐
客户端 b ──> manager 服务线程 b ─┼──> 同一个 queue.queue
客户端 c ──> manager 服务线程 c ─┘
这些服务线程都位于同一个 manager 服务端进程中,因此它们共享服务端进程的内存,也能访问同一个 queue.queue。
这正是 queue.queue 擅长处理的情况:
同一个进程中的多个线程
│
▼
同一个 queue.queue
6. manager 会自动给队列加锁吗?
manager 会保护自己的内部状态,例如:
- 网络连接;
- 对象注册信息;
- 代理对象;
- 引用计数。
但是,manager 不会自动给注册对象的每个方法加一把全局锁。
也就是说,manager 不会自动执行下面这样的逻辑:
with global_lock:
task_queue.put("任务")
多个客户端请求可能在不同的 manager 服务线程中同时执行,因此真正保存数据的对象仍然需要保证线程安全。
queue.queue 自己已经实现了线程锁,所以多个 manager 服务线程同时调用它的 put() 或 get() 时,不会把内部数据弄乱。
它们的职责可以这样理解:
manager
│
│ 负责把客户端命令送到服务端
▼
queue.queue
│
│ 负责安全地执行队列操作
▼
服务端内存中的数据
因此,不是 manager 自动把普通对象变成了线程安全对象,而是 queue.queue 本身就是线程安全的。
7. 为什么不使用multiprocessing.queue?
不是完全不能使用,而是在典型的 manager 分布式架构中通常没有必要。
manager 已经解决了跨进程、跨机器通信的问题。
如果服务端再使用 multiprocessing.queue,就相当于又增加了一层进程间通信机制。
使用 multiprocessing.queue 时,数据路径类似:
客户端
│
│ manager 网络通信
▼
manager 服务端
│
│ 再进行序列化和管道传输
▼
multiprocessing.queue
而使用 queue.queue 时,数据路径更加直接:
客户端
│
│ manager 网络通信
▼
manager 服务端
│
│ 直接操作服务端内存
▼
queue.queue
multiprocessing.queue 内部通常还需要:
- 对数据进行序列化;
- 使用操作系统管道;
- 使用进程锁和信号量;
- 使用后台线程传输数据。
这些机制的目的是让不同进程能够直接传递数据。
但在 manager 分布式架构中,客户端并不直接操作服务端队列。manager 已经把客户端命令传到服务端了,所以没有必要再使用一层进程间管道。
因此:
queue.queue 不是负责分布式通信的,manager 才负责分布式通信。
queue.queue 只是 manager 服务端进程内部保存数据的线程安全容器。
8. 三种场景总结
多线程
一个进程
├── 线程 a ─┐
├── 线程 b ─┼──> queue.queue
└── 线程 c ─┘
使用:
import queue q = queue.queue()
本机多进程
进程 a ──> 操作系统管道 ──> 进程 b
使用:
from multiprocessing import queue q = queue()
多机器分布式通信
客户端 a ─┐
客户端 b ─┼──> manager 服务端 ──> queue.queue
客户端 c ─┘
使用:
import queue from multiprocessing.managers import basemanager task_queue = queue.queue()
9. 最终结论
选择哪一种 queue,取决于谁在直接操作队列。
同一个进程中的多个线程
└── queue.queue
本机多个进程直接传递数据
└── multiprocessing.queue
多个机器通过 manager 访问服务端数据
└── manager + queue.queue
最重要的理解是:
queue.queue 让同一个进程中的多个线程安全地操作共享数据。
multiprocessing.queue 通过管道把数据从一个进程传递到另一个进程。
在分布式 manager 场景中,客户端并不直接操作队列,只是通过网络向服务端发送命令;真正的队列和数据仍然位于 manager 服务端进程中。
以上就是一文详解python中queue.queue和multiprocessing.queue的区别的详细内容,更多关于python queue.queue和multiprocessing.queue区别的资料请关注代码网其它相关文章!
发表评论