首页 > 解决方案 > Python 3.7 和 3.8 之间 Python thread.join() 的区别

问题描述

我有一个小的 Python 程序,它在 Python 3.7 和 Python 3.8 中的行为不同。我很难理解为什么。Python 3.8的#threading 更改日志没有解释这一点。

这是代码:

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 相同:除非所有非守护线程都已加入,否则程序不应退出。

有人可以帮我理解发生了什么吗?

标签: pythonmultithreading

解决方案


_shutdown()从 Python 版本 3.7.3 到 3.7.4的线程行为有一个未记录的变化。

我是这样找到它的:

为了追踪这个问题,我首先使用了inspect包来找出join()Python 3.7.3 运行时中的线程是谁。我修改了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()

来自Python 3.7.3的CPython 源代码_shutdown(),第 1279-1282 行:

    t = _pickSomeNonDaemonThread()
    while t:
        t.join()
        t = _pickSomeNonDaemonThread()

退出join()时,该代码会在所有非守护线程上调用!MainThread

该实现在 Python 3.7.4 中进行了更改

为了验证这些发现,我从源代码构建了 Python 3.7.4。它的行为确实不同。它使线程按预期运行,并且join()不调用该函数。

这显然没有记录在Python 3.7.4 的发行说明和Python 3.8的变更日志中。

- 编辑:

正如 MisterMiyagi 在评论中指出的那样,有人可能会争辩说,扩展该join()功能并将其用于发出终止信号并不是join(). 恕我直言,这取决于口味。但是,应该记录在 Python 3.7.3 和之前的版本中,join()由 Python 运行时在系统退出时调用,而对3.7.4这种情况的更改不再是这种情况。如果记录得当,它将从一开始就解释这种行为。


推荐阅读