使用Python多进程管理器(BaseManager/SyncManager)与远程机器共享队列时出现Broken Pipe
在过去一个月里,我们在使用Python 2.6.x的多进程包时遇到了一个持续的问题,特别是在尝试在几台不同的(Linux)电脑之间共享一个队列时。我也直接向Jesse Noller提问过,因为我们在StackOverflow、Python文档、源代码或其他地方都没有找到能解释这个问题的资料。
我们的工程师团队一直没能解决这个问题,我们也向很多Python用户组的人询问过,但都没有结果。我希望有人能给我们一些启发,因为我觉得我们可能做错了什么,但又因为太接近这个问题而看不到真正的原因。
这里是症状:
Traceback (most recent call last):
File "/var/django_root/dev/com/brightscope/data/processes/daemons/deferredupdates/servers/queue_server.py", line 65, in get_from_queue
return queue, queue.get(block=False)
File "<string>", line 2, in get
File "/usr/local/lib/python2.6/multiprocessing/managers.py", line 725, in _callmethod
conn.send((self._id, methodname, args, kwds))
IOError: [Errno 32] Broken pipe
(我展示了我们的代码如何在一个由扩展SyncManager的管理器托管的共享队列对象上调用queue.get()。)
这个问题的奇怪之处在于,如果我们在一台机器上(我们称之为机器A
)连接到这个共享队列,即使有很多并发进程,我们似乎从来不会遇到问题。只有当我们从其他机器(我们称之为机器B和C
)连接到队列,并同时进行大量的进出队列操作时,才会出现问题。
就好像Python的多进程包在处理本地连接时(尽管它们仍然使用相同的manager.connect()连接方法)表现得很好,但当从机器B或C
中的至少一台机器同时进行远程连接时,我们就会遇到“Broken pipe”错误。
经过我们团队的阅读,我们认为这个问题可能与锁定有关。我们曾想,也许不应该使用Queue.Queue
,而应该使用multiprocessing.Queue
,但我们切换后问题依然存在(我们还注意到SyncManager自己的共享队列是Queue.Queue的一个实例)。
我们对如何调试这个问题感到很困惑,因为这个问题很难重现,但确实发生得相当频繁(如果我们从队列中插入和获取很多项目,每天会发生很多次)。
我们创建的方法get_from_queue
尝试以随机的睡眠间隔重试从队列获取项目大约10次,但似乎如果第一次失败,接下来的十次也都会失败(这让我怀疑.register()和.connect()到管理器可能并没有给服务器提供另一个套接字连接,但我也无法通过阅读文档或查看Python内部源代码来确认这一点)。
有没有人能提供一些建议,让我们知道该去哪里查找或如何追踪实际发生了什么?
在发生“broken pipe”时,我们如何使用multiprocessing.BaseManager
或multiprocessing.SyncManager
启动一个新连接?
我们如何才能从一开始就防止“broken pipe”问题的发生?
5 个回答
确保你的电脑内存足够支持这个程序。我刚刚增加了分配的内存,结果“破管道”错误就解决了。
我也遇到过同样的问题,即使是在本地连接时使用的是 Python 2.7.1。经过一天的调试,我找到了原因和解决办法:
原因:BaseProxy 类有一个线程本地存储,它会缓存连接,这样在未来的连接中会重用这个连接,导致即使在创建新的管理器时也会出现“断开的管道”错误。
解决办法:在重新连接之前,先删除缓存的连接。在引发异常的那一行代码中加上 try-except 语句,然后再尝试连接。
from multiprocessing.managers import BaseProxy
...
if address in BaseProxy._address_to_local:
del BaseProxy._address_to_local[address][0].connection
address
是用来连接多进程管理器的主机名或 IP 地址。如果你没有明确设置,它通常应该是 "localhost"
。
顺便说一下,如果有人遇到同样的错误,经过和Python核心开发团队的Ask Solem和Jesse Noller深入讨论后,发现这实际上是当前Python 2.6.x(可能还有2.7+和3.x)中的一个bug。他们正在研究可能的解决方案,未来的Python版本可能会修复这个问题。