我有一个小Python程序,它在Python 3.7和Python 3.8中的表现不同。我很难理解为什么。Python 3.8的#threading changelog并没有对此进行解释。
这是代码:
import time
from threading import Event, Thread
class StoppableWorker(Thread):
def __init__(self):
super(StoppableWorker, self).__init__()
self.daemon = False
self._stop_event = Event()
def join(self, *args, **kwargs):
self._stop_event.set()
print("join called")
super(StoppableWorker, self).join(*args, **kwargs)
def run(self):
while not self._stop_event.is_set():
time.sleep(1)
print("hi")
if __name__ == "__main__":
t = StoppableWorker()
t.start()
print("main done.")
当我在Python 3.7.3(Debian Buster(中运行此程序时,我看到以下输出:
python test.py
main done.
join called
hi
程序自行退出。我不知道为什么要调用join()
。来自3.7:的守护程序文档
当没有活动的非守护进程线程时,整个Python程序将退出。
但很明显,线程应该还活着。
当我在Python 3.8.6(Arch(中运行此程序时,我得到了预期的行为。也就是说,程序一直在运行:
python test.py
main done.
hi
hi
hi
hi
...
3.8的守护进程文档与3.7相同:除非所有非守护进程线程都已加入,否则程序不应退出。
有人能帮我了解发生了什么事吗?
从Python版本3.7.3到3.7.4,线程_shutdown()
的行为发生了未记录的更改。
以下是我的发现:
为了跟踪这个问题,我首先使用inspect包来找出谁是Python 3.7.3运行时中的线程join()
。我修改了join()
函数以获得一些输出:
...
def join(self, *args, **kwargs):
self._stop_event.set()
c = threading.current_thread()
print(f"join called from thread {c}")
print(f"calling function: {inspect.stack()[1][3]}")
super(StoppableWorker, self).join(*args, **kwargs)
...
当使用Python 3.7.3执行时,将打印:
main done.
join called from thread <_MainThread(MainThread, stopped 139660844881728)>
calling function: _shutdown
hi
因此,已经停止的MainThread
调用join()
方法。MainThread
中负责的功能是_shutdown()
。
来自_shutdown()
的Python 3.7.3的CPython源代码,第1279-1282行:
t = _pickSomeNonDaemonThread()
while t:
t.join()
t = _pickSomeNonDaemonThread()
当MainThread
退出时,该代码在所有非守护进程线程上调用join()
该实现在Python 3.7.4中进行了更改。
为了验证这些发现,我从源代码构建了Python 3.7.4。它的行为确实有所不同。它保持线程按预期运行,并且不调用join()
函数。
这显然没有在Python 3.7.4的发行说明中记录,也没有在Python 3.8的变更日志中记录。
--编辑:
正如MisterMiyagi在评论中指出的那样,有人可能会争辩说,扩展join()
函数并将其用于信号终止不是对join()
的正确使用。IMHO,这是你的口味。然而,应该记录的是,在Python 3.7.3及之前的版本中,join()
是由Python运行时在系统退出时调用的,而随着3.7.4
的更改,情况不再如此。如果记录得当,它将从一开始就解释这种行为。
新增功能仅列出新功能。在我看来,这些更改就像是一个bug修复。https://docs.python.org/3.7/whatsnew/3.7.html在顶部附近具有CCD_ 17链路。考虑到@Felix的回答中的研究,我们应该看看3.7.4中发布的错误修复。https://docs.python.org/3.7/whatsnew/changelog.html#python-3-7-4-租赁日期-1
这可能是问题所在:https://bugs.python.org/issue36402bpo-36402:修复Python在等待线程时关闭时的竞争条件。等待所有非守护进程线程的Python线程状态被删除(加入所有非守护程序线程(,而不是等待非守护进程Python线程完成。