Search code examples
pythonmultithreadingloopsterminate

How do I stop a thread in python which itself is being called inside a loop?


This seems like a particularly confusing question based on the other similar answers I found on SO. I have code similar to the following:

def parentFunction():
    # Other code
    while True:
        var1, var2 = anotherFunction1() # Getting client details after listening on open port
        threading.Thread(target = anotherFunction2, args=(var1, var2)).start()
        childFunction(var1,var2)
        print("PRINT #1: Running in Parent Function") # This only prints once for some reason

def childFunction(var1, var2):
    threading.Timer(10, childFunction, args=(var1,var2)).start()
    print("PRINT #2: Running in child function") # Prints every 10 seconds
    
    # Other code 

    if (someConditionIsMet):
        print("PRINT #3: Exiting") 
        end_process_and_exit_here()

So basically, when I ran the parentFunction(), I would go into a neverending loop where ever 10 seconds, my console would print "PRINT #2: Running in child function". When the someConditionIsMet was true, my console would print "PRINT #3: Exiting" but then it wouldn't exit. Hence, my loop would carry on forever. I am not sure if it's relevant, but parts of the code has a Threading.Lock as well.

Where I have written end_process_and_exit_here() above, I tried using several methods to kill a thread such as

  1. Raising exceptions and setting flags - These assume that I have started my thread outside of my loop so it's not comparable.
  2. Even this qn about looping threads assumes the thread isnt being looped
  3. Killing using join or stop - stop() was not an option I could access. join() was available but it didn't work i.e. after it was called, the next thread (PRINT #2) continued printing.
  4. Other answers suggesting the use of signals (1) (2), also didn't work.
  5. Using sys.exit() or break in different parts of my code also did not result in the threads stopping.

Is there any method for me to easily exit from such a looping thread?

Note: I need to use threading and not multiprocessing.


Solution

  • So as an update, I have managed to resolve this issue. The problem with the other answer stated by me (shown below) is that just .cancel() by itself only seemed to work for one timer thread. But as can be seen in the problem, childFunction() itself calls childFunction() and can also be called by the parentFunction, meaning that there may be multiple timer threads.

    What worked for my specific case was naming my threads as below:

    t1 = threading.Timer(10, childFunction, args=(var1,var2,number))
    t1.name = t1.name + "_timer" + str(number)
    t1.start()
    

    Thereafter, I could cancel all timer threads that were created from this process by:

    for timerthread in threading.enumerate():
        if timerthread.name.endswith('timer' + str(number)):
            timerthread.cancel()
    

    Below is the ORIGINAL METHOD I USED WHICH CAUSED MANY ISSUES:

    I'm not certain if this is a bad practice (in fact I feel it may be based on the answers linked in the question saying that we should never 'kill a thread'). I'm sure there are reasons why this is not good and I'd appreciate anyone telling me why. However, the solution that ultimately worked for me was to use .cancel().

    So first change would be to assign your thread Timer to a variable instead of calling it directly. So instead of threading.Timer(10, childFunction, args=(var1,var2)).start(), it should be

    t = threading.Timer(10, childFunction, args=(var1,var2))
    t.start()
    

    Following that, instead of end_process_and_exit_here(), you should use t.cancel(). This seems to work and stops all threads mid-process. However, the bad thing is that it doesn't seem to carry on with other parts of the program.