Python多线程+;多处理断开管道错误(子进程未退出?)

Python多线程+;多处理断开管道错误(子进程未退出?),python,multithreading,multiprocessing,Python,Multithreading,Multiprocessing,当采用multiprocessing.JoinableQueue的线程生成进程时,我遇到BrokenPipeError。这似乎是在程序完成工作并试图退出后发生的,因为它完成了所有它应该做的事情。这意味着什么,有没有办法解决这个问题/安全地忽略它 import requests import multiprocessing from multiprocessing import JoinableQueue from queue import Queue import threading cla

当采用multiprocessing.JoinableQueue的线程生成进程时,我遇到BrokenPipeError。这似乎是在程序完成工作并试图退出后发生的,因为它完成了所有它应该做的事情。这意味着什么,有没有办法解决这个问题/安全地忽略它

import requests
import multiprocessing
from multiprocessing import JoinableQueue
from queue import Queue
import threading


class ProcessClass(multiprocessing.Process):
    def __init__(self, func, in_queue, out_queue):
        super().__init__()
        self.in_queue = in_queue
        self.out_queue = out_queue
        self.func = func

    def run(self):
        while True:
            arg = self.in_queue.get()
            self.func(arg, self.out_queue)
            self.in_queue.task_done()


class ThreadClass(threading.Thread):
    def __init__(self, func, in_queue, out_queue):
        super().__init__()
        self.in_queue = in_queue
        self.out_queue = out_queue
        self.func = func

    def run(self):
        while True:
            arg = self.in_queue.get()
            self.func(arg, self.out_queue)
            self.in_queue.task_done()


def get_urls(host, out_queue):
    r = requests.get(host)
    out_queue.put(r.text)
    print(r.status_code, host)


def get_title(text, out_queue):
    print(text.strip('\r\n ')[:5])


if __name__ == '__main__':
    def test():

        q1 = JoinableQueue()
        q2 = JoinableQueue()

        for i in range(2):
            t = ThreadClass(get_urls, q1, q2)
            t.daemon = True
            t.setDaemon(True)
            t.start()

        for i in range(2):
            t = ProcessClass(get_title, q2, None)
            t.daemon = True
            t.start()

        for host in ("http://ibm.com", "http://yahoo.com", "http://google.com", "http://amazon.com", "http://apple.com",):
            q1.put(host)

        q1.join()
        q2.join()

    test()
    print('Finished')
程序输出:

200 http://ibm.com
<!DOC
200 http://google.com
<!doc
200 http://yahoo.com
<!DOC
200 http://apple.com
<!DOC
200 http://amazon.com
<!DOC
Finished
Exception in thread Thread-2:
Traceback (most recent call last):
  File "C:\Python\33\lib\multiprocessing\connection.py", line 313, in _recv_bytes
    nread, err = ov.GetOverlappedResult(True)
BrokenPipeError: [WinError 109]

The pipe has been ended

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Python\33\lib\threading.py", line 901, in _bootstrap_inner
    self.run()
  File "D:\Progs\Uspat\uspat\spider\run\threads_test.py", line 31, in run
    arg = self.in_queue.get()
  File "C:\Python\33\lib\multiprocessing\queues.py", line 94, in get
    res = self._recv()
  File "C:\Python\33\lib\multiprocessing\connection.py", line 251, in recv
    buf = self._recv_bytes()
  File "C:\Python\33\lib\multiprocessing\connection.py", line 322, in _recv_bytes
    raise EOFError
EOFError
....
200http://ibm.com
(对其他螺纹剪切相同的错误。)


如果我将JoinableQueue切换到多线程部分的queue.queue,一切都会修复,但原因是什么?

发生这种情况是因为您在主线程退出时将后台线程阻塞在
多处理.queue.get
调用中,但它只在某些情况下发生:

  • 当主线程退出时,守护进程线程正在运行并阻塞
    多处理.Queue.get
  • 一个
    多处理。进程正在运行
  • 多处理
    上下文不是
    “fork”
  • 例外情况是,
    多处理.JoinableQueue
    正在侦听的
    连接的另一端在
    get()
    调用的内部发送了
    EOF
    时。通常这意味着
    连接的另一侧已关闭。这在关机期间发生是有意义的——Python在退出解释器之前清理所有对象,清理的一部分包括关闭所有打开的
    连接
    对象。我还没有弄清楚的是,为什么只有当一个
    多处理.Process
    已经生成(而不是分叉,这就是为什么默认情况下它不会在Linux上发生)并且仍在运行时才会发生(而且总是发生)。如果我创建了一个只在
    while
    循环中休眠的
    多处理.Process
    ,我甚至可以复制它。它根本不接受任何
    队列
    对象。无论出于何种原因,出现一个正在运行的派生子进程似乎可以保证引发异常。它可能只是导致事物被破坏的顺序恰好适合于种族条件的发生,但这只是一个猜测

    在任何情况下,使用
    queue.queue
    而不是
    multiprocessing.JoinableQueue
    都是一种很好的修复方法,因为实际上不需要
    multiprocessing.queue
    。您还可以通过向后台线程和/或后台进程的队列发送sentinel来确保后台线程和/或后台进程在主线程之前关闭。因此,让两个
    运行
    方法检查哨兵:

    def run(self):
        for arg in iter(self.in_queue.get, None):  # None is the sentinel
            self.func(arg, self.out_queue)
            self.in_queue.task_done()
        self.in_queue.task_done()
    
    完成后再派哨兵:

        threads = []
        for i in range(2):
            t = ThreadClass(get_urls, q1, q2)
            t.daemon = True
            t.setDaemon(True)
            t.start()
            threads.append(t)
    
        p = multiprocessing.Process(target=blah)
        p.daemon = True
        p.start()
        procs = []
        for i in range(2):
            t = ProcessClass(get_title, q2, None)
            t.daemon = True
            t.start()
            procs.append(t)
    
        for host in ("http://ibm.com", "http://yahoo.com", "http://google.com", "http://amazon.com", "http://apple.com",):
            q1.put(host)
    
        q1.join()
        # All items have been consumed from input queue, lets start shutting down.
        for t in procs:
            q2.put(None)
            t.join()
        for t in threads:
            q1.put(None)
            t.join()
        q2.join()
    

    发生这种情况的原因是,当主线程退出时,后台线程将阻塞在
    多处理.Queue.get
    调用中,但这种情况仅在某些情况下发生:

  • 当主线程退出时,守护进程线程正在运行并阻塞
    多处理.Queue.get
  • 一个
    多处理。进程正在运行
  • 多处理
    上下文不是
    “fork”
  • 例外情况是,
    多处理.JoinableQueue
    正在侦听的
    连接的另一端在
    get()
    调用的内部发送了
    EOF
    时。通常这意味着
    连接的另一侧已关闭。这在关机期间发生是有意义的——Python在退出解释器之前清理所有对象,清理的一部分包括关闭所有打开的
    连接
    对象。我还没有弄清楚的是,为什么只有当一个
    多处理.Process
    已经生成(而不是分叉,这就是为什么默认情况下它不会在Linux上发生)并且仍在运行时才会发生(而且总是发生)。如果我创建了一个只在
    while
    循环中休眠的
    多处理.Process
    ,我甚至可以复制它。它根本不接受任何
    队列
    对象。无论出于何种原因,出现一个正在运行的派生子进程似乎可以保证引发异常。它可能只是导致事物被破坏的顺序恰好适合于种族条件的发生,但这只是一个猜测

    在任何情况下,使用
    queue.queue
    而不是
    multiprocessing.JoinableQueue
    都是一种很好的修复方法,因为实际上不需要
    multiprocessing.queue
    。您还可以通过向后台线程和/或后台进程的队列发送sentinel来确保后台线程和/或后台进程在主线程之前关闭。因此,让两个
    运行
    方法检查哨兵:

    def run(self):
        for arg in iter(self.in_queue.get, None):  # None is the sentinel
            self.func(arg, self.out_queue)
            self.in_queue.task_done()
        self.in_queue.task_done()
    
    完成后再派哨兵:

        threads = []
        for i in range(2):
            t = ThreadClass(get_urls, q1, q2)
            t.daemon = True
            t.setDaemon(True)
            t.start()
            threads.append(t)
    
        p = multiprocessing.Process(target=blah)
        p.daemon = True
        p.start()
        procs = []
        for i in range(2):
            t = ProcessClass(get_title, q2, None)
            t.daemon = True
            t.start()
            procs.append(t)
    
        for host in ("http://ibm.com", "http://yahoo.com", "http://google.com", "http://amazon.com", "http://apple.com",):
            q1.put(host)
    
        q1.join()
        # All items have been consumed from input queue, lets start shutting down.
        for t in procs:
            q2.put(None)
            t.join()
        for t in threads:
            q1.put(None)
            t.join()
        q2.join()
    

    感谢您的全面回答(附带说明,可能对某人有帮助:我想使用multiprocessing.JoinableQueue而不是queue.queue,以便能够将参数从多处理部分传递回应用程序的多线程部分,尽管我上面的示例中没有这样的代码),感谢您的全面回答(旁注,可能对某些人有帮助:我想使用multiprocessing.JoinableQueue而不是queue.queue,以便能够将参数从多处理部分传递回应用程序的多线程部分,尽管在我上面的示例中没有这样的代码)