'multiprocessing.resource_sharer'中的AttributeError'DupFd'| Python多处理+线程

时间:2018-09-09 18:43:03

标签: python multithreading concurrency multiprocessing python-multiprocessing

我正在尝试在多个执行I / O绑定任务的threading.Thread与多个执行CPU绑定任务的multiprocessing.Process之间进行通信。每当线程为某个进程找到工作时,它将与multiprocessing.Queue的发送端一起放在multiprocessing.Pipe(duplex=False)上。然后,这些过程将发挥自己的作用,并通过管道将结果发送回线程。此程序似乎在大约70%的情况下都有效,另外30%的我收到了AttributeError: Can't get attribute 'DupFd' on <module 'multiprocessing.resource_sharer' from '/usr/lib/python3.5/multiprocessing/resource_sharer.py'>

要复制:

import multiprocessing
import threading
import time

def thread_work(work_queue, pipe):
    while True:
        work_queue.put((threading.current_thread().name,  pipe[1]))
        received = pipe[0].recv()
        print("{}: {}".format(threading.current_thread().name, threading.current_thread().name == received))
        time.sleep(0.3)

def process_work(work_queue):
    while True:
        thread, pipe = work_queue.get()
        pipe.send(thread)

work_queue = multiprocessing.Queue()
for i in range(0,3):
    receive, send = multiprocessing.Pipe(duplex=False)
    t = threading.Thread(target=thread_work, args=[work_queue, (receive, send)])
    t.daemon = True
    t.start()

for i in range(0,2):
    p = multiprocessing.Process(target=process_work, args=[work_queue])
    p.daemon = True
    p.start()

time.sleep(5)

我看了一下多处理source code,但是不明白为什么会发生此错误。 我尝试使用queue.Queue或带有duplex=True的管道(默认),但在错误中找不到模式。有人知道如何调试吗?

1 个答案:

答案 0 :(得分:2)

您在这里分叉一个已经是多线程的主进程。一般而言,这是有问题的。

  

这实际上很容易出现问题(不仅限于Python)。规则是“分叉之后而不是之前的线程”。否则,线程执行器使用的锁将在各个进程之间重复。如果其中一个进程在拥有锁的同时死亡,则使用该锁的所有其他进程将死锁-Raymond Hettinger

引发错误的原因显然是在子进程中管道的文件描述符复制失败。

要解决此问题,请在主进程仍为单线程的情况下创建子进程,或使用其他start_method创建新进程,例如“ spawn”(Windows上的默认设置)或“ forkserver” '(如果有)。

  

forkserver

     

当程序启动并选择forkserver start方法时,服务器进程将启动。从此以后,每当需要新进程时,父进程就连接到服务器并请求它派生一个新进程。 fork服务器进程是单线程的,因此使用os.fork()是安全的。没有多余的资源被继承。

     

在Unix平台上可用,该平台支持通过Unix管道传递文件描述符。 docs

您可以通过以下方式指定另一个start_method:

  

multiprocessing.set_start_method(方法)   设置用于启动子进程的方法。方法可以是“ fork”,“ spawn”或“ forkserver”。

     

请注意,该调用最多应调用一次,并且应在主模块的if name ==' main '子句中进行保护。 docs

有关特定start_method的基准(在Ubuntu 18.04上),请查看here