【问题标题】:Ending non-daemon threads when shutting down an interactive python session关闭交互式 python 会话时结束非守护线程
【发布时间】:2018-05-23 10:31:49
【问题描述】:

请考虑下面的代码:

#! /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”并运行“python3 -i test.py”后,我得到了一个交互式会话,线程定期打印“线程活着!”消息。

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

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

有没有办法做到这一点?例如,在 REPL 循环终止之后,就在主线程退出之前执行的某个钩子可用吗?

【问题讨论】:

  • 你知道atexit吗?

标签: python python-multithreading


【解决方案1】:

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

查看 Python 源代码,事实证明,在关闭时,Python 会在完成进程关闭之前尝试 join() 所有非守护线程。哪种是有意义的,除了如果有记录的方式来通知这些线程,这将更加有用。没有。

但是,可以重新实现一个自己的从 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,它忽略了“线程”模块文档 (https://docs.python.org/3/library/threading.html) 中的以下段落:

Thread 类表示一个在单独的 控制线程。有两种方法可以指定活动:通过 将可调用对象传递给构造函数,或者通过覆盖 子类中的 run() 方法。 没有其他方法(除了 构造函数)应该在子类中被覆盖。换句话说,只有 覆盖该类的 __init__() 和 run() 方法

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

【讨论】:

  • 这其实挺有用的。可能应该避免它,但如果程序无论如何都将退出,即使它破坏了一些东西也没关系。
  • 我们可以把这个提交给python开发者考虑吗?允许覆盖连接听起来像是一个有用的功能,除非这会破坏我们看不到的东西(比如在 Windows 上?)
  • 不幸的是,这在 Python 3.7.4 之后不再起作用。 “线程”模块的 _shutdown 方法已更改,不再调用 join()。叹息。
  • 我对引入此更改的 PR 发表了评论:github.com/python/cpython/pull/13948
【解决方案2】:

我不确定这是否可以接受,但我能看到您的问题的唯一方法是从您的脚本启动交互式控制台,而不是使用 -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()

【讨论】:

  • 我很欣赏您提出的解决方法,但不幸的是,在我的实际案例中,这不起作用;我需要一个不涉及启动专用解释器的解决方案。
  • 我害怕那里有某种限制,无论如何我都想提供解决方案。很高兴您能够解决问题。
【解决方案3】:

我认为您可以这样做的方法是使用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()

【讨论】:

  • 不,那行不通。在 Python 关闭过程尝试加入所有活动的非守护线程后调用 atexit 例程,如果它们不终止则阻塞。
  • 我很明确地说我不想在我的问题中这样做。
  • 我相信你说过“我希望能够正确地结束/加入线程,因为在我正在解决的现实案例中,我想很好地终止网络连接。” ...您有很多选择可以终止网络连接...使该线程成为守护程序并不会阻止您加入...
  • 感谢您的建议,但它确实没有回答我的问题,即“关闭交互式 python 会话时结束非守护线程”。
  • "有没有办法做到这一点?例如,在 REPL 循环终止后,就在主线程退出之前,是否有可用的钩子?" -- 在REPL之后,在主线程存在之前执行了一个钩子,让我看看我能不能找到它的名字:)。
【解决方案4】:

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

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()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-30
    • 2012-07-24
    • 1970-01-01
    • 2012-10-18
    • 1970-01-01
    相关资源
    最近更新 更多