" EOF错误"在程序退出时使用多处理队列和线程

时间:2018-03-10 13:04:03

标签: python multithreading python-3.x queue multiprocessing

我无法理解为什么这个简单的程序最后会引发EOFError

我正在使用Queue()Thread()进行通信,我想自动并干净地终止我的计划atexit

import threading
import multiprocessing
import atexit

class MyClass:

    def __init__(self):
        self.queue = None
        self.thread = None

    def start(self):
        self.queue = multiprocessing.Queue()
        self.thread = threading.Thread(target=self.queued_writer, daemon=True)
        self.thread.start()

        # Remove this: no error
        self.queue.put("message")

    def queued_writer(self):
        while 1:
            msg = self.queue.get()
            print("Message:", msg)
            if msg is None:
                break

    def stop(self):
        self.queue.put(None)
        self.thread.join()

instance = MyClass()

atexit.register(instance.stop)

# Put this before register: no error
instance.start()

这引起了:

Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "test.py", line 21, in queued_writer
    msg = self.queue.get()
  File "/usr/lib/python3.6/multiprocessing/queues.py", line 94, in get
    res = self._recv_bytes()
  File "/usr/lib/python3.6/multiprocessing/connection.py", line 216, in recv_bytes
    buf = self._recv_bytes(maxlength)
  File "/usr/lib/python3.6/multiprocessing/connection.py", line 407, in _recv_bytes
    buf = self._recv(4)
  File "/usr/lib/python3.6/multiprocessing/connection.py", line 383, in _recv
    raise EOFError
EOFError

此外,此代码段的行为很奇怪:如果我删除self.queue.put("message")行,则不会引发错误并且线程会成功退出。同样,如果在instance.start()之前调用atexit.register(),这似乎也有效。

有谁知道错误从何而来?

修改:我注意到使用SimpleQueue()似乎会使错误消失。

3 个答案:

答案 0 :(得分:2)

问题来自多个atexit.register()来电之间的冲突。

文件说明:

  

atexit按照注册顺序运行这些函数;如果您注册ABC,则在翻译终止时,他们将按照CBA的顺序投放

     

[...]

     

假设通常在较高级别的模块之前导入较低级别的模块,因此必须在以后进行清理。

首先导入multiprocessing然后调用atexit.register(my_stop),您可能希望在任何内部终止程序之前执行停止功能......但事实并非如此,因为atexit.register()可以动态调用。

在本例中,multiprocessing库使用_exit_function函数,该函数用于干净地关闭内部线程和队列。此功能已在atexit at the module level中注册,但模块仅加载once the Queue() object is initialized

因此,MyClass停止功能在multiprocessing之前注册,因此{/ 1>}在之后被称为 { {1}}。

在终止期间,instance.stop关闭内部管道连接,因此如果线程稍后尝试使用关闭的读取连接调用_exit_function,则会引发_exit_function。只有当Python没有时间自动终止.get()线程时才会发生这种情况,即“慢速”退出函数(如EOFError或本例daemon)是在通常的关闭程序之后注册并运行。由于某种原因,写连接关闭被延迟,因此time.sleep(0.1)不会立即引发错误。

至于为什么对代码段的小修改使其有效:thread.join()没有.put()因此内部管道稍后关闭。 SimpleQueue的内部线程在调用第一个Finalizer之前不会启动,因此删除它意味着没有要关闭的管道。通过导入Queue强制注册也是可能的。

答案 1 :(得分:0)

要实现此目的,您可以在班级中定义__enter____exit__,并使用with语句创建您的实例:

import threading
import multiprocessing


class MyClass:

    def __init__(self):
        self.queue = None
        self.thread = None

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.stop()

    def start(self):
        self.queue = multiprocessing.Queue()
        self.thread = threading.Thread(target=self.queued_writer, daemon=True)
        self.thread.start()

    def queued_writer(self):
        while 1:
            msg = self.queue.get()
            print("Message:", str(msg))
            if msg is None:
                break

    def put(self, msg):
        self.queue.put(msg)

    def stop(self):
        self.queue.put(None)
        self.thread.join()


with MyClass() as instance:
    instance.start()
    print('Thread stopped: ' + str(instance.thread._is_stopped))
    instance.put('abc')

print('Thread stopped: ' + str(instance.thread._is_stopped))

以上代码作为输出:

Thread stopped: False
Message: abc
Message: None
Thread stopped: True

答案 2 :(得分:0)

问题的表面答案非常简单,当主进程结束时,queued_writer进程仍在等待条目写入队列,将EOF发送到self.queue.get打开的打开阻塞连接。 / p>

这提出了为什么atexit.register似乎没有做到这一点的问题,但我不知道原因。