Why does sys.exit() not exit when called inside a thread in Python?

PythonPython 2.6

Python Problem Overview


I am confused as to why the following code snippet would not exit when called in the thread, but would exit when called in the main thread.

import sys, time
from threading import Thread

def testexit():
	time.sleep(5)
	sys.exit()
	print "post thread exit"

t = Thread(target = testexit)
t.start()
t.join()
print "pre main exit, post thread exit"
sys.exit()
print "post main exit"

The docs for sys.exit() state that the call should exit from Python. I can see from the output of this program that "post thread exit" is never printed, but the main thread just keeps on going even after the thread calls exit.

Is a separate instance of the interpreter being created for each thread, and the call to exit() is just exiting that separate instance? If so, how does the threading implementation manage access to shared resources? What if I did want to exit the program from the thread (not that I actually want to, but just so I understand)?

Python Solutions


Solution 1 - Python

sys.exit() raises the SystemExit exception, as does thread.exit(). So, when sys.exit() raises that exception inside that thread, it has the same effect as calling thread.exit(), which is why only the thread exits.

Solution 2 - Python

> What if I did want to exit the program from the thread?

For Linux:
os.kill(os.getpid(), signal.SIGINT)

This sends a SIGINT to the main thread which raises a KeyboardInterrupt. With that you have a proper cleanup. Also you can register a handler, if you want to react differently.

The above does not work on Windows, as you can only send a SIGTERM signal, which is not handled by Python and has the same effect as os._exit().

For Windows:

You can use:

os._exit()

This will exit the entire process without any cleanup. If you need cleanup, you need to communicate with the main thread in another way.

Solution 3 - Python

> What if I did want to exit the program > from the thread?

Apart from the method Deestan described you can call os._exit (notice the underscore). Before using it make sure that you understand that it does no cleanups (like calling __del__ or similar).

Solution 4 - Python

> What if I did want to exit the program > from the thread (not that I actually > want to, but just so I understand)?

My preferred method is Erlang-ish message passing. Slightly simlified, I do it like this:

import sys, time
import threading
import Queue # thread-safe

class CleanExit:
  pass

ipq = Queue.Queue()

def testexit(ipq):
  time.sleep(5)
  ipq.put(CleanExit)
  return

threading.Thread(target=testexit, args=(ipq,)).start()
while True:
  print "Working..."
  time.sleep(1)
  try:
    if ipq.get_nowait() == CleanExit:
      sys.exit()
  except Queue.Empty:
    pass

Solution 5 - Python

Is the fact that "pre main exit, post thread exit" is printed what's bothering you?

Unlike some other languages (like Java) where the analog to sys.exit (System.exit, in Java's case) causes the VM/process/interpreter to immediately stop, Python's sys.exit just throws an exception: a SystemExit exception in particular.

Here are the docs for sys.exit (just print sys.exit.__doc__):

> Exit the interpreter by raising SystemExit(status).
> If the status is omitted or None, it defaults to zero (i.e., success).
> If the status is numeric, it will be used as the system exit status.
> If it is another kind of object, it will be printed and the system
> exit status will be one (i.e., failure).

This has a few consequences:

  • in a thread it just kills the current thread, not the entire process (assuming it gets all the way to the top of the stack...)
  • object destructors (__del__) are potentially invoked as the stack frames that reference those objects are unwound
  • finally blocks are executed as the stack unwinds
  • you can catch a SystemExit exception

The last is possibly the most surprising, and is yet another reason why you should almost never have an unqualified except statement in your Python code.

Solution 6 - Python

_thread.interrupt_main() is available since Python 3.7 (optional before that)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionShabbyrobeView Question on Stackoverflow
Solution 1 - PythonrpkellyView Answer on Stackoverflow
Solution 2 - PythonChrisView Answer on Stackoverflow
Solution 3 - PythonHelmut GrohneView Answer on Stackoverflow
Solution 4 - PythonDeestanView Answer on Stackoverflow
Solution 5 - PythonLaurence GonsalvesView Answer on Stackoverflow
Solution 6 - PythonpsorensonView Answer on Stackoverflow