Search code examples
pythonmultithreadingpython-3.xthread-safetypython-multithreading

threading - sentinel value or Event to break loops


I can think of two ways to break out of a loop in a Python thread, minimal examples below:

1 - Use a sentinel value

from threading import Thread, Event
from time import sleep

class SimpleClass():

    def do_something(self):
        while self.sentinel:
            sleep(1)
            print('loop completed')

    def start_thread(self):
        self.sentinel = True
        self.th = Thread(target=self.do_something)
        self.th.start()

    def stop_thread(self):
        self.sentinel = False
        self.th.join()

simpleinstance = SimpleClass()
simpleinstance.start_thread()
sleep(5)
simpleinstance.stop_thread()

2 - Use an Event

from threading import Thread, Event
from time import sleep

class SimpleThread(Thread):

    def __init__(self):
        super(SimpleThread, self).__init__()

        self.stoprequest = Event()

    def run(self):
        while not self.stoprequest.isSet():
            sleep(1)
            print('loop completed')

    def join(self, timeout=None):
        self.stoprequest.set()
        super(SimpleThread, self).join(timeout)

simpleinstance = SimpleThread()
simpleinstance.start()
sleep(5)
simpleinstance.join()

In the Python documentation, it discusses events but not the simpler 'sentinel value' approach (which I see used in many threading answers on Stack Overflow).

Is there any disadvantage to using the sentinel value?

Specifically, could it cause errors (I have never had one but I imagine if you tried to change the value of the sentinel at exactly the same moment it was being read for the while loop then something could break (or maybe the CPython GIL would save me in this case). What is considered best (safest) practice?


Solution

  • If you look at the source of Event, you can see that the function you are using don't have any more value for you:

    class Event:
        def __init__(self):
            self._cond = Condition(Lock())
            self._flag = False
    
        def is_set(self):
            return self._flag
    
        def set(self):
            with self._cond:
                self._flag = True
                self._cond.notify_all() # No more-value, because you are not using Event.wait
    

    So in your case Event is just a fancy wrapper for a sentinel value with no actually use, that will also slow down your operation time by a really tiny amount.

    Events are only useful if you use their wait method.