1

The following code:

import threading
import time
from functools import partial
from itertools import count

def daemon_loop(sleep_interval, stop_event):
    for j in count():
        print(j)
        if stop_event.is_set():
            break
        time.sleep(sleep_interval)
        print('Slept %s' % sleep_interval)
    print('Prod terminating')

if __name__ == '__main__':
    stop_event = threading.Event() #https://stackoverflow.com/a/41139707/281545
    target = partial(daemon_loop, sleep_interval=2, stop_event=stop_event)
    prod_thread = threading.Thread(target=target,
                                   # daemon=True
                                   )

    try:
        prod_thread.start()
        while True:
            time.sleep(10)
    except KeyboardInterrupt:
        print('Terminating...')
        stop_event.set()

prints on a keyboard interrupt:

C:\Users\MrD\.PyCharm2018.2\config\scratches>c:\_\Python363-64\python.exe thread_daemon.py
0
Slept 2
1
Terminating...
Slept 2
2
Prod terminating

Uncommenting the # daemon=True line results in the prod_thread being ended immediately:

C:\Users\MrD\.PyCharm2018.2\config\scratches>c:\_\Python363-64\python.exe thread_daemon.py
0
Slept 2
1
Terminating...

My question is what is the preferred/more pythonic way to deal with thread termination - should I drop the Event machinery and just mark the thread as daemon or is there some edge case I miss?

See:

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
  • 3
    This depends purely on the nature of your solution - if threads are expected to exit cleanly (i.e. save progress, clean up, etc.) and optionally report on their status to the main thread, then you need a mechanism to allow you to do that like an event check. If your threads are just dumb workers that need not to exit cleanly, using daemon threads is perfectly fine. – zwer Sep 17 '18 at 10:21

1 Answers1

3

I haven't done enough Python to give you a "Pythonic" answer, but I can answer in more general programming terms.

Firstly, I'm not a fan of terminating threads. There are cases where it is safe and OK, such as your example here - but terminating in the middle of print writing its output would feel a little dirty.

Secondly, if you want to continue using sleep (which I'm also not a fan of) you could repeat your if stop_event.is_set(): and break after the sleep. (Don't move the code, copy it.) The main problem with sleep in this case is that it will wait the full sleep_interval even if the event is set during that time.

Thirdly - and my preference - instead of using sleep, do a wait on the event with a timeout. If the event is not set during the wait, wait returns false after waiting the timeout period. If the event is set before or during the wait, wait returns true immediately (that is, it aborts the timeout, giving you fast, clean shutdown of the thread.)

So your code would look something like this:

def daemon_loop(sleep_interval, stop_event):
    for j in count():
        print(j)
        if stop_event.wait(sleep_interval):
            break
        print('Slept %s' % sleep_interval)
    print('Prod terminating')
Allison Lock
  • 2,375
  • 15
  • 17