Please consider the code below:
#! /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()
After saving this as 'test.py' and running 'python3 -i test.py', I get an interactive session, where the thread regularly prints the "Thread is alive!" message.
When I press control-D or run exit(), the python3 process does not terminate because the thread is still running. I want to fix that.
However, I do not want to make the thread a daemon thread -- I want to be able to end / join the thread properly, because in the real-life case I'm solving, I want to terminate a network connection nicely.
Is there a way to do this? For example, is some hook available that is executed after the REPL loop terminates, just prior to the exit of the main thread?
Okay I found a way to do this myself.
Looking at the Python source-code, it turns out that at shutdown, Python attempts to join() all non-daemonic threads before completing the shutdown of the process. Which sort-of makes sense, except that this would be much more useful if there were a documented way to signal those threads. Which there isn't.
However, it is possible to re-implement the join() method of one's own class derived from Thread, which can that serve as a way to notify the subthread that it is supposed to shut itself down:
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()
However, this way of addressing the issue is a pretty much a hack, and it ignores the following passage from the 'threading' module docs (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.
It does, however, work beautifully.
I am not sure if this would be acceptable, but the only way I can see around your problem is to start the interactive console from your script instead of with -i
flag. Then you would be able to carry on with your program to do the exit, after you terminate your interactive session:
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()
This you would execute without -i
flag, only as
python3 test.py
It would still give you the interactive console as with python -i
, but now the script executes exit_gracefully()
after the interactive shell exits.
I think the way you can do this is to register a cleanup function using atexit
and set your thread as a daemon, eg:
#! /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()