关闭交互式 python 会话时结束非守护线程

Ending non-daemon threads when shutting down an interactive python session

请考虑以下代码:

#! /usr/bin/env python3

import threading
import time

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self._quit_flag = False
    def run(self):
        while not self._quit_flag:
            print("Thread is alive!")
            time.sleep(0.500)
    def request_quit(self):
        self._quit_flag = True

mt = MyThread()
mt.start()

将其保存为 'test.py' 和 运行ning 'python3 -i test.py' 后,我得到一个交互式会话,其中线程定期打印 "Thread is alive!" 消息。

当我按下 control-D 或 运行 exit() 时,python3 进程没有终止,因为线程仍在 运行ning。我想解决这个问题。

但是,我不想让线程成为守护线程 -- 我希望能够正确结束/加入线程,因为在现实生活中我正在解决,我想很好地终止网络连接。

有办法吗?例如,在 REPL 循环终止后,在主线程退出之前执行的一些挂钩是否可用?

我不确定这是否可以接受,但我能解决您的问题的唯一方法是从您的脚本而不是使用 -i 标志启动交互式控制台。然后你就可以继续你的程序来完成退出,在你终止你的交互式会话之后:

import threading
import time
from code import InteractiveConsole

def exit_gracefully():
    print("exiting gracefully now")
    global mt
    mt.request_quit()
    mt.join()


class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self._quit_flag = False
    def run(self):
        while not self._quit_flag:
            print("Thread is alive!")
            time.sleep(0.500)
    def request_quit(self):
        self._quit_flag = True

mt = MyThread()
mt.start()
InteractiveConsole(locals=locals()).interact()
exit_gracefully()

你将在没有 -i 标志的情况下执行,仅作为

python3 test.py

它仍然会像 python -i 一样为您提供交互式控制台,但现在脚本会在交互式 shell 退出后执行 exit_gracefully()

好的,我自己找到了方法。

查看 Python 源代码,发现在关闭时,Python 尝试在完成进程关闭之前加入()所有非守护线程。哪一种有意义,除了如果有一种记录的方式来向这些线程发出信号,这会更有用。哪个没有。

但是,可以重新实现自己的 class 派生自 Thread 的 join() 方法,可以作为一种通知方式应该自行关闭的子线程:

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self._quit_flag = False
    def __del__(self):
        print("Bye bye!")
    def run(self):
        while not self._quit_flag:
            print("Thread is alive!",  threading.active_count(), threading.main_thread().is_alive())
            for t in threading.enumerate():
                print(t.is_alive())
            time.sleep(0.500)
    def request_quit(self):
        self._quit_flag = True

    def join(self):
        self.request_quit()
        super().join()

但是,这种解决问题的方法几乎是一种 hack,它忽略了 'threading' 模块文档 (https://docs.python.org/3/library/threading.html) 中的以下段落:

The Thread class represents an activity that is run in a separate thread of control. There are two ways to specify the activity: by passing a callable object to the constructor, or by overriding the run() method in a subclass. No other methods (except for the constructor) should be overridden in a subclass. In other words, only override the __init__() and run() methods of this class.

不过,它确实工作得很好。

我认为你可以做到这一点的方法是使用 atexit 注册一个清理函数并将你的线程设置为守护进程,例如:

#! /usr/bin/env python3

import threading
import time
import atexit

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()

        # this ensures that atexit gets called.
        self.daemon = True
        self._quit_flag = False

    def run(self):
        while not self._quit_flag:
            print("Thread is alive!")
            time.sleep(0.500)
        print("cleanup can also go here")

    def request_quit(self):
        print("cleanup can go here")
        self._quit_flag = True


mt = MyThread()
def cleanup():
    mt.request_quit()
    mt.join()
    print("even more cleanup here, so many options!")

atexit.register(cleanup)
mt.start()

只需在后台创建一个新的关闭线程:

import threading
import time

class MyThread(threading.Thread):
    def __init__(self):
        super().__init__()
        self._quit_flag = False
    def run(self):
        while not self._quit_flag:
            print("Thread is alive!")
            time.sleep(0.500)
        print("shutting down.")
    def request_quit(self):
        self._quit_flag = True

mt = MyThread()
mt.start()

# Shutdown thread
threading.Thread(
    target=lambda: threading.main_thread().join() or mt.request_quit()
    ).start()