By Brian Fitzgerald
Here are some notes on the behavior of python functions sys.exit() vs os._exit() in a threaded program.
from threading import Thread, currentThread
from time import sleep, time
import sys
import atexit
def pt(str=None):
print('el=%ss th=%s: %s' % (
round(time() - t0, 1),
currentThread().name, str)
)
def testexit():
pt('sleeping')
sleep(int(currentThread().getName()))
pt('calling sys.exit')
sys.exit(0)
pt('returning')
def alldone():
pt('all done')
t0 = time()
atexit.register(alldone)
threads = []
for i in [1, 2]:
th = Thread(target=testexit)
th.setName(i)
th.setDaemon(False)
th.start()
threads.append(th)
for th in threads:
pt('waiting to join thread th=%s' % th.name)
th.join()
pt('main: joined to th=%s' % th.name)
In the threads, the sleep time equals the thread id, i.e. 1 or 2 seconds. We have arranged that function alldone will execute when the program exits.
"C:\Users\Brian Fitzgerald\PycharmProjects\blog\venv\Scripts\python.exe" "C:/Users/Brian Fitzgerald/PycharmProjects/blog/threadexit.py" el=0.0s th=1: sleeping el=0.0s th=2: sleeping el=0.0s th=MainThread: waiting to join thread th=1 el=1.0s th=1: calling sys.exit el=1.0s th=MainThread: main: joined to th=1 el=1.0s th=MainThread: waiting to join thread th=2 el=2.0s th=2: calling sys.exit el=2.0s th=MainThread: main: joined to th=2 el=2.0s th=MainThread: all done Process finished with exit code 0
Notes:
- sys.exit() causes the thread to exit
- statement “pt(‘returning’)” is not reached
- The main thread finishes
- Function alldone is executed
- The output is identical if th.setDaemon(True)
Now changing “import sys” to “import os”, and “sys.exit(0)” to “os._exit(0)”:
from threading import Thread, currentThread
from time import sleep, time
import os
import atexit
def pt(str=None):
print('el=%ss th=%s: %s' % (
round(time() - t0, 1),
currentThread().name, str)
)
def testexit():
pt('sleeping')
sleep(int(currentThread().getName()))
pt('calling os._exit')
os._exit(0)
pt('returning')
def alldone():
pt('all done')
t0 = time()
atexit.register(alldone)
threads = []
for i in [1, 2]:
th = Thread(target=testexit)
th.setName(i)
th.setDaemon(False)
th.start()
threads.append(th)
for th in threads:
pt('waiting to join thread th=%s' % th.name)
th.join()
pt('main: joined to th=%s' % th.name)
"C:\Users\Brian Fitzgerald\PycharmProjects\blog\venv\Scripts\python.exe" "C:/Users/Brian Fitzgerald/PycharmProjects/blog/threadexit.py" el=0.0s th=1: sleeping el=0.0s th=2: sleeping el=0.0s th=MainThread: waiting to join thread th=1 el=1.0s th=1: calling os._exit Process finished with exit code 0
Notes:
- MainThread is terminated while waiting to join thread 1.
- thread 2 is terminated while sleeping
- alldone is not reached
- The output is identical if th.setDaemon(True)
Beware that os._exit can terminate a thread while it is in an exception handler:
def testexit():
pt('try')
try:
raise Exception()
except:
pt('sleeping')
sleep(int(currentThread().getName()))
pt('done except')
finally:
pt('done finally')
pt('calling os._exit')
os._exit(0)
pt('returning')
"C:\Users\Brian Fitzgerald\PycharmProjects\blog\venv\Scripts\python.exe" "C:/Users/Brian Fitzgerald/PycharmProjects/blog/threadexit.py" el=0.0s th=1: try el=0.0s th=1: sleeping el=0.0s th=MainThread: waiting to join thread th=1 el=0.0s th=2: try el=0.0s th=2: sleeping el=1.0s th=1: done except el=1.0s th=1: done finally el=1.0s th=1: calling os._exit Process finished with exit code 0
Note:
- In thread 2, pt(‘done except’) is not reached.
os._exit can also interrupt a finally block:
def testexit():
pt('try')
try:
raise Exception()
except:
pt('done except')
finally:
pt('sleeping')
sleep(int(currentThread().getName()))
pt('done finally')
pt('calling os._exit')
os._exit(0)
pt('returning')
"C:\Users\Brian Fitzgerald\PycharmProjects\blog\venv\Scripts\python.exe" "C:/Users/Brian Fitzgerald/PycharmProjects/blog/threadexit.py" el=0.0s th=1: try el=0.0s th=1: done except el=0.0s th=1: sleeping el=0.0s th=2: try el=0.0s th=2: done except el=0.0s th=MainThread: waiting to join thread th=1 el=0.0s th=2: sleeping el=1.0s th=1: done finally el=1.0s th=1: calling os._exit Process finished with exit code 0
Note:
- In thread 2, pt(‘done finally’) is not reached