Python Threading Tutorial

A beginner’s guide to threading in python

“The truth is like salt. Men want to taste a little, but too much makes everyone sick.” ― Joe Abercrombie, The Heroes

1. Introduction

Let us learn about python threading. Threading refers to executing a task in a separate thread of control, freeing up the main thread for other activities. It is very useful for executing time-consuming tasks without holding up the rest of the program. Typical applications include servers which handle each request in a thread and programs with a user-interface which execute complex tasks in a background thread.

2. Getting Started

Here is an example for getting started, which illustrates the basic concepts. A thread is an object of the class threading.Thread.

It requires a function for execution when it starts. (It is also possible to subclass threading.Thread to provide an action to run. See below.)

When created, a thread starts in a not-yet-started state, and should be started using the method start(). When the task function is done, the thread exits and can be waited for using the method join().

To illustrate the passing of arguments to the task function, we pass the number of seconds to sleep as an argument.

import threading
import random, time

def runner(secs):
    print threading.current_thread(), 'begin sleep(', secs, ')'
    time.sleep(secs)
    print threading.current_thread(), 'end sleep(', secs, ')'

threads = []
for x in xrange(0, 5):
    t = threading.Thread(target = runner, args = (random.randint(1, 10),))
    threads.append(t)
    t.start()

print 'joining ..'
while threading.active_count() > 1:
    for t in threads:
        t.join()
        print t, 'is done.'
print 'all done.'

3. Subclassing Thread

It is also possible to start a thread by subclassing threading.Thread. Depending on the design of your application, you may prefer this approach. Here, you extend threading.Thread and provide the implementation of your task in the run() method.

import threading
import random, time

class MyTask(threading.Thread):
    def __init__(self, sleepFor):
        self.secs = sleepFor
        threading.Thread.__init__(self)

    def run(self):
        print self, 'begin sleep(', self.secs, ')'
        time.sleep(self.secs)
        print self, 'end sleep(', self.secs, ')'

And here is the usage of the class defined above.

tasks = []
for x in xrange(0, 5):
    t = MyTask(random.randint(1, 10))
    tasks.append(t)
    t.start()

print 'joining ..'
while threading.active_count() > 1:
    for t in tasks:
        t.join()
        print t, 'is done.'
print 'all done.'

4. Using a Lambda as the Task Function

Is it possible to use a lambda as a task function? You bet it is! Use a construct of the form target = lambda x: x**2. Here is an example

import threading
import random, time

threads = []
for x in xrange(0, 5):
    t = threading.Thread(target = lambda x: time.sleep(x), args = (random.randint(1, 10),))
    threads.append(t)
    t.start()

print 'joining ..'
while threading.active_count() > 1:
    for t in threads:
        t.join()
        print t, 'is done.'
print 'all done.'

Note that this example can also be re-written as:

    t = threading.Thread(target = time.sleep, args = (random.randint(1, 10),))

5. Interrupting a Thread

How do you interrupt a thread? There are no direct ways of interrupting a thread (short of killing it), and you have to build in the support for stopping a thread. In the following code sample, we use the threading.Event object to signal a thread to just “quit it”.

Here is the task routine, which is where the task is being performed. Assuming the task is being performed in a loop, you need to incorporate a check using the is_set() method on the event object to see if it is set, and if so, terminate the loop. Here we also wait for 0.5secs before checking again to simulate some computation.

import threading

def runner(ev):
    print threading.current_thread(), 'started.'
    while not ev.is_set():
        ev.wait(0.5)
    print threading.current_thread(), 'completed.'

When starting the thread, we need to create the Event object and pass it as the argument to the task runner.

threads = []
ev = threading.Event()
for x in xrange(0, 5):
    t = threading.Thread(target = runner, args = (ev,))
    threads.append(t)
    t.start()

The worker threads are now up and running. When you want to interrupt the execution, set the event object. Here, we add a random wait before setting the event object.

t = random.randint(5, 20)
print 'waiting for:', t, 'secs.'
time.sleep(t)
print 'signalling quit ..'
ev.set()

And of course, for a clean exit, wait for the threads to exit.

print 'joining ..'
while threading.active_count() > 1:
    for t in threads:
        t.join()
        print t, 'is done.'
print 'all done.'

Conclusion

That brings us to the end of this gentle introduction to threading in python. A thread of execution can be created by creating a Thread object while providing a function to run. You can also subclass Thread and override the run() method.