Matthew Moisen
Matthew Moisen

Reputation: 18279

Most Pythonic way to kill a thread after some period of time

I would like to run a process in a thread (which is iterating over a large database table). While the thread is running, I just want the program to wait. If that thread takes longer then 30 seconds, I want to kill the thread and do something else. By killing the thread, I mean that I want it to cease activity and release resources gracefully.

I figured the best way to do this was through a Thread()'s join(delay) and is_alive() functions, and an Event. Using the join(delay) I can have my program wait 30 seconds for the thread to finish, and by using the is_alive() function I can determine if the thread has finished its work. If it hasn't finished its work, the event is set, and the thread knows to stop working at that point.

Is this approach valid, and is this the most pythonic way to go about my problem statement?

Here is some sample code:

import threading
import time

# The worker loops for about 1 minute adding numbers to a set
# unless the event is set, at which point it breaks the loop and terminates
def worker(e):
    data = set()
    for i in range(60):
        data.add(i)
        if not e.isSet():
            print "foo"
            time.sleep(1)
        else:
            print "bar"
            break

e = threading.Event()
t = threading.Thread(target=worker, args=(e,))
t.start()

# wait 30 seconds for the thread to finish its work
t.join(30)
if t.is_alive():
    print "thread is not done, setting event to kill thread."
    e.set()
else:
    print "thread has already finished."

Upvotes: 24

Views: 42102

Answers (3)

Sugandha Jain
Sugandha Jain

Reputation: 353

I was also struggling to close a thread that was waiting to receive a notification. Tried solution given here by user5737269 but it didn't really work for me. It was getting stuck in second join statement(without timeout one). Struggled a lot but didn't find any solution to this problem. Got this solution after thinking sometime: My thread is waiting to receive a message in que. I want to close this thread, if no notification is received for 20 seconds. So, after 20 seconds, I am writing a message to this que so that thread terminates on its own. Here's code:

 q = Queue.Queue()
 t.join(20)
    if t.is_alive():
        print("STOPPING THIS THREAD ....")
        q.put("NO NOTIFICATION RECEIVED")
        t.join(20)
    else:
        print("Thread completed successfully!!")

This worked for me.. Hope this idea helps someone!

Upvotes: 0

slumtrimpet
slumtrimpet

Reputation: 3267

I'm way late to this game, but I've been wrestling with a similar question and the following appears to both resolve the issue perfectly for me AND lets me do some basic thread state checking and cleanup when the daemonized sub-thread exits:

import threading
import time
import atexit

def do_work():

  i = 0
  @atexit.register
  def goodbye():
    print ("'CLEANLY' kill sub-thread with value: %s [THREAD: %s]" %
           (i, threading.currentThread().ident))

  while True:
    print i
    i += 1
    time.sleep(1)

t = threading.Thread(target=do_work)
t.daemon = True
t.start()

def after_timeout():
  print "KILL MAIN THREAD: %s" % threading.currentThread().ident
  raise SystemExit

threading.Timer(2, after_timeout).start()

Yields:

0
1
KILL MAIN THREAD: 140013208254208
'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568]

Upvotes: 6

user5737269
user5737269

Reputation:

Using an Event in this case is works just fine as the signalling mechanism, and is actually recommended in the threading module docs.

If you want your threads to stop gracefully, make them non-daemonic and use a suitable signalling mechanism such as an Event.

When verifying thread termination, timeouts almost always introduce room for error. Therefore, while using the .join() with a timeout for the initial decision to trigger the event is fine, final verification should be made using a .join() without a timeout.

# wait 30 seconds for the thread to finish its work
t.join(30)
if t.is_alive():
    print "thread is not done, setting event to kill thread."
    e.set()
    # The thread can still be running at this point. For example, if the 
    # thread's call to isSet() returns right before this call to set(), then
    # the thread will still perform the full 1 second sleep and the rest of 
    # the loop before finally stopping.
else:
    print "thread has already finished."

# Thread can still be alive at this point. Do another join without a timeout 
# to verify thread shutdown.
t.join()

This can be simplified to something like this:

# Wait for at most 30 seconds for the thread to complete.
t.join(30)

# Always signal the event. Whether the thread has already finished or not, 
# the result will be the same.
e.set()

# Now join without a timeout knowing that the thread is either already 
# finished or will finish "soon."
t.join()

Upvotes: 13

Related Questions