TUTORIAL
Multithreading
This section introduces Python multithreading
On this page
Python’s multithreading does not truly run multiple threads at the same time. This is because of the constraint imposed by the GIL — the Global Interpreter Lock — introduced by the Python interpreter. Python can have multiple threads, but each thread, when it runs, needs to wait for the GIL to be released, then acquire the GIL and lock it. While that thread is running, the other threads cannot run, because the GIL is locked; once that thread releases the GIL, the other threads can execute. So, no matter how many cores the CPU has, at any given moment only one thread is running. This is why Python’s multithreading is sometimes criticized as fake multithreading.
However, Python multithreading still has its uses, namely in I/O-bound applications, where the speed of reading I/O is a bottleneck. You can have one thread reading I/O and another thread doing other work, so that, while waiting for the I/O, other data processing can be carried out in parallel.
If the program is compute-bound rather than I/O-bound, Python multithreading brings no essential improvement in performance.
Python Multithreading
Creating a Thread threading.Thread
Using the threading module, you can start a new execution thread by creating a new threading.Thread and assigning it the function to execute:
import threading
def Test():
print "Hello threading!"
my_thread = threading.Thread(target=Test)
The target parameter references the function (or callable object) to run. Before start is called on the Thread object, the thread will not begin executing.
Starting a Thread Thread.start()
my_thread.start() # prints 'Hello threading!'
Now my_thread has already run and terminated, and calling start again will produce a RuntimeError. If you want to run your thread as a daemon process, you need to set daemon=True, or set my_thread.daemon to True before calling it; this will make the thread run silently in the background as a daemon.
Joining a Thread Thread.join()
If you split a large task into several small tasks and want to run them at the same time, but need to wait for all of that work to complete before continuing with the tasks that follow, then Thread.join() is the method you are looking for.
For example, suppose you want to download multiple pages of a website and compile them into a single page. You can do it like this:
import requests
from threading import Thread
from queue import Queue
q = Queue(maxsize=20)
def put_page_to_q(page_num):
q.put(requests.get("http://some-website.com/page_%s.html" % page_num))
def compile(q):
# magic function that needs all pages before being able to be executed
if not q.full():
raise ValueError
else:
print("Done compiling!")
threads = []
for page_num in range(20):
t = Thread(target=requests.get, args=(page_num,))
t.start()
threads.append(t)
# Next, join all threads to make sure all threads are done running before
# we continue. join() is a blocking call (unless specified otherwise using
# the kwarg blocking=False when calling join)
for t in threads:
t.join()
# Call compile() now, since all threads have completed
compile(q)
Creating a Custom Thread Class
Using the threading.Thread class, we can subclass a new custom Thread class. We must override the run method in the subclass.
from threading import Thread
import time
class Sleepy(Thread):
def run(self):
time.sleep(5)
print("Hello form Thread")
if __name__ == "__main__":
t = Sleepy()
t.start() # start method automatic call Thread class run method.
# print 'The main program continues to run in foreground.'
t.join()
print("The main program continues to run in the foreground.")
The output order you see is like this
Hello form Thread
The main program continues to run in the foreground.
But if you comment out t.join(), the output order becomes
The main program continues to run in the foreground.
Hello form Thread
This also verifies the join method introduced above: only after all the threads have finished executing will it continue with the tasks that follow.
Communication Between Threads
If there are multiple threads in the code and some data needs to be shared between them, then we need to communicate safely between them. This requires using the queue type in Queue.
from queue import Queue
from threading import Thread
# create a data producer
def producer(output_queue):
while True:
data = data_computation()
output_queue.put(data)
# create a consumer
def consumer(input_queue):
while True:
# retrieve data (blocking)
data = input_queue.get()
# do something with the data
# indicate data has been consumed
input_queue.task_done()
Create producer and consumer threads using a shared queue,
q = Queue()
t1 = Thread(target=consumer, args=(q,))
t2 = Thread(target=producer, args=(q,))
t1.start()
t2.start()