Python 3.7和3.8在Python thread.join()中的差异



我有一个小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线程完成。

最新更新