In this tutorial, you’ll learn how to use Python’s built-in threading module to explore multithreading capabilities in Python.
Starting with the basics of processes and threads, you’ll learn how multithreading works in Python—while understanding the concepts of concurrency and parallelism. You’ll then learn how to start and run one or more threads in Python using the built-in threading
module.
Let’s get started.
Processes vs. Threads: Differences
What Is a Process?
A process is any instance of a program that needs to run.
It can be anything – a Python script or a web browser such as Chrome to a video-conferencing application. If you launch the Task Manager on your machine and navigate to Performance –> CPU, you’ll be able to see the processes and threads that are currently running on your CPU cores.
Understanding Processes and Threads
Internally, a process has a dedicated memory that stores the code and data corresponding to the process.
A process consists of one or more threads. A thread is the smallest sequence of instructions that the operating system can execute, and it represents the flow of execution.
Each thread has its own stack and registers but not a dedicated memory. All the threads associated with a process can access the data. Therefore, data and memory are shared by all the threads of a process.
In a CPU with N cores, N processes can execute in parallel at the same instant of time. However, two threads of the same process can never execute in parallel- but can execute concurrently. We’ll address the concept of concurrency vs. parallelism in the next section.
Based on what we’ve learned so far, let’s summarize the differences between a process and a thread.
Feature | Process | Thread |
Memory | Dedicated memory | Shared memory |
Mode of execution | Parallel, concurrent | Concurrent; but not parallel |
Execution handled by | Operating System | CPython Interpreter |
Multithreading in Python
In Python, the Global Interpreter Lock (GIL) ensures that only one thread can acquire the lock and run at any point in time. All threads should acquire this lock to run. This ensures that only a single thread can be in execution—at any given point in time—and avoids simultaneous multithreading.
For example, consider two threads, t1
and t2
, of the same process. Because threads share the same data, when t1
is reading a particular value k
, t2
may modify the same value k
. This can lead to deadlocks and undesirable results. But only one of the threads can acquire the lock and run at any instant. Therefore, GIL also ensures thread safety.
So how do we achieve multithreading capabilities in Python? To understand this, let’s discuss the concepts of concurrency and parallelism.
Concurrency vs. Parallelism: An Overview
Consider a CPU with more than one core. In the illustration below, the CPU has four cores. This means that we can have four different operations running in parallel at any given instant.
If there are four processes, then each of the processes can run independently and simultaneously on each of the four cores. Let’s assume that each process has two threads.
To understand how threading works, let us switch from multicore to single-core processor architecture. As mentioned, only a single thread can be active at a particular execution instance; but the processor core can switch between the threads.
For example, I/O-bound threads often wait for I/O operations: reading in user input, database reads, and file operations. During this waiting time, I/O-bound thread can release the lock so that the other thread can run. The waiting time can also be a simple operation such as sleeping for n
seconds.
In summary: During wait operations, the thread releases the lock, enabling the processor core to switch to another thread. The earlier thread resumes execution after the waiting period is complete. This process, where the processor core switches between the threads concurrently, facilitates multithreading. ✅
If you want to implement process-level parallelism in your application, consider using multiprocessing instead.
Python Threading Module: First Steps
Python ships with a threading
module that you can import into the Python script.
import threading
To create a thread object in Python, you can use the Thread
constructor: threading.Thread(...)
. This is the generic syntax that suffices for most threading implementations:
threading.Thread(target=...,args=...)
Here,
target
is the keyword argument denoting a Python callableargs
is the tuple of arguments that the target takes in.
You’ll need Python 3.x to run the code examples in this tutorial. Download the code and follow along.
Define and Run Threads in Python
Let’s define a thread that runs a target function.
The target function is some_func
.
import threading
import time
def some_func():
print("Running some_func...")
time.sleep(2)
print("Finished running some_func.")
thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())
Let’s parse what the above code snippet does:
- It imports the
threading
and thetime
modules. - The function
some_func
has descriptiveprint()
statements and includes a sleep operation for two seconds:time.sleep(n)
causes the function to sleep forn
seconds. - Next, we define a thread
thread_1
with the target assome_func
.threading.Thread(target=...)
creates a thread object. - Note: Specify the name of the function and not a function call; use
some_func
and notsome_func()
. - Creating a thread object does not start a thread; calling the
start()
method on the thread object does. - To get the number of active threads, we use the
active_count()
function.
The Python script is running on the main thread, and we are creating another thread (thread1
) to run the function some_func
; so the active thread count is two, as seen in the output:
# Output
Running some_func...
2
Finished running some_func.
If we take a closer look at the output, we see that upon starting thread1
, the first print statement runs. But during the sleep operation, the processor switches to the main thread and prints out the number of active threads—without waiting for thread1
to finish executing.
Waiting for Threads to Finish Execution
If you want thread1
to finish the execution, you can call the join()
method on it after starting the thread. Doing so will wait for thread1
to finish execution without switching to the main thread.
import threading
import time
def some_func():
print("Running some_func...")
time.sleep(2)
print("Finished running some_func.")
thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())
Now, thread1
has finished executing before we print out the active thread count. So only the main thread is running, which means the active thread count is one. ✅
# Output
Running some_func...
Finished running some_func.
1
How to Run Multiple Threads in Python
Next, let’s create two threads to run two different functions.
Here, count_down
is a function that takes in a number as the argument and counts down from that number to zero.
def count_down(n):
for i in range(n,-1,-1):
print(i)
We define count_up
, another Python function that counts from zero up to a given number.
def count_up(n):
for i in range(n+1):
print(i)
📑 When using the
range()
function with the syntaxrange(start, stop, step)
, the end pointstop
is excluded by default.– To count down from a specific number to zero, you can use a negative
step
value of -1 and set thestop
value to -1 so that zero is included.– Similarly, to count up to
n
, you have to set thestop
value ton + 1
. Because the default values ofstart
andstep
are 0 and 1, respectively, you may userange(n + 1)
to get the sequence 0 through n.
Next, we define two threads, thread1
and thread2
to run the functions count_down
and count_up
, respectively. We add print
statements and sleep
operations for both the functions.
When creating the thread objects, notice that the arguments to the target function should be specified as a tuple—to the args
parameter. As both the functions (count_down
and count_up
) take in one argument, you’ll have to insert a comma explicitly after the value. This ensures the argument is still passed in as a tuple, as the subsequent elements are inferred as None
.
import threading
import time
def count_down(n):
for i in range(n,-1,-1):
print("Running thread1....")
print(i)
time.sleep(1)
def count_up(n):
for i in range(n+1):
print("Running thread2...")
print(i)
time.sleep(1)
thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()
In the output:
- The function
count_up
runs onthread2
and counts up to 5 starting at 0. - The
count_down
function runs onthread1
counts down from 10 to 0.
# Output
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0
You can see that thread1
and thread2
execute alternatively, as both of them involve a wait operation (sleep). Once the count_up
function has finished counting up to 5, thread2
is no longer active. So we get the output corresponding to only thread1
.
Summing Up
In this tutorial, you’ve learned how to use Python’s built-in threading module to implement multithreading. Here’s a summary of the key takeaways:
- The Thread constructor can be used to create a thread object. Using threading.Thread(target=<callable>,args=(<tuple of args>)) creates a thread that runs the target callable with arguments specified in args.
- The Python program runs on a main thread, so the thread objects you create are additional threads. You can call active_count() function returns the number of active threads at any instance.
- You can start a thread using the start() method on the thread object and wait until it finishes execution using the join() method.
You can code additional examples by tweaking the waiting times, trying for a different I/O operation, and more. Be sure to implement multithreading in your upcoming Python projects. Happy coding!🎉