关闭交互式 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()
请考虑以下代码:
#! /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()