Home > other >  Stopping thread earily in Python depending on state of conditional variable
Stopping thread earily in Python depending on state of conditional variable

Time:10-07

In a Python code I have a function that is running in a separate thread and looks something like this:

import threading
import sleep


class Foo:
    def __init__(self):
        self._thread = None
        self._event = threading.Event()

    def start():
        if not self._event.is_set():
            self._event.set()
            self._thread = threading.Thread(target=self._some_function)
            self._thread.start()

    def stop():
        if self._event.is_set():
            self._event.clear()
            self._thread.join()
            self._thread = None

    def _some_function():
        while self._event.is_set():
            do_something()
            time.sleep(2)


if __name__ == "__main__":
    foo = Foo()
    foo.start()
    foo.stop()

In the worst case it takes Foo.stop() full 2 seconds to stop the code, which is a bit problematic. I'm trying to find a way in Python to solve this, i.e. abort the sleep if self._event.is_set() does not return True anymore.

I found some threads that relate to this question, i.e. this and this, but I don't think they can be really applied to this situation...

Any advice on this?

CodePudding user response:

As I know, you can't awake a sleeping subthread when you use time.sleep(), but you can awake a sleeping mainthread, using signal mechanism.

If you want to awake a thread before the timeout expire, you can use wait() method from threading.Event() class. This method gives you control to awake a subthread/mainthread before the timeout expire. Below changes will solve your problem ;

import threading
import time


class Foo:
    def __init__(self):
        self._thread = None
        self._event = threading.Event()
        self._event2 = threading.Event()

    def start(self):
        if not self._event.is_set():
            self._event.set()
            self._thread = threading.Thread(target=self._some_function)
            self._thread.start()

    def stop(self):
        if self._event.is_set():
            self._event2.set()

            self._event.clear()
            self._thread.join()
            self._thread = None

    def _some_function(self):
        while self._event.is_set():
            self._event2.wait(2)
            self._event2.clear()
            do_something()
            


if __name__ == "__main__":
    foo = Foo()
    foo.start()
    foo.stop()

CodePudding user response:

I think the following should do it, even if the logic is a bit backwards (at least to what I would intuitively do):

import threading
import sleep


class Foo:
    def __init__(self):
        self._thread = None
        self._stop_thread = threading.Event()
        self._stop_thread.set()

    def start():
        if self._stop_thread.is_set():
            self._stop_thread.clear()
            self._thread = threading.Thread(target=self._some_function)
            self._thread.start()

    def stop():
        if not self._stop_thread.is_set():
            self._stop_thread.set()
            self._thread.join()
            self._thread = None

    def _some_function():
        while not self._stop_thread.is_set():
            self._stop_thread.wait(amount_of_time)
            do_something()

if __name__ == "__main__":
    foo = Foo()
    foo.start()
    foo.stop()

This avoids the need for two conditional variables.

  • Related