我无法理解为什么这个简单的程序最后会引发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()
似乎会使错误消失。
答案 0 :(得分:2)
问题来自多个atexit.register()
来电之间的冲突。
文件说明:
atexit
按照注册顺序运行这些函数;如果您注册A
,B
和C
,则在翻译终止时,他们将按照C
,B
,A
的顺序投放[...]
假设通常在较高级别的模块之前导入较低级别的模块,因此必须在以后进行清理。
首先导入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
似乎没有做到这一点的问题,但我不知道原因。