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
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.
|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,
t2, of the same process. Because threads share the same data, when
t1 is reading a particular value
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
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.
To create a thread object in Python, you can use the
threading.Thread(...). This is the generic syntax that suffices for most threading implementations:
targetis the keyword argument denoting a Python callable
argsis 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
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
- The function
print()statements and includes a sleep operation for two seconds:
time.sleep(n)causes the function to sleep for
- Next, we define a thread
thread_1with the target as
threading.Thread(target=...)creates a thread object.
- Note: Specify the name of the function and not a function call; use
- 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
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())
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.
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)
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 syntax
range(start, stop, step), the end point
stopis excluded by default.
– To count down from a specific number to zero, you can use a negative
stepvalue of -1 and set the
stopvalue to -1 so that zero is included.
– Similarly, to count up to
n, you have to set the
n + 1. Because the default values of
stepare 0 and 1, respectively, you may use
range(n + 1)to get the sequence 0 through n.
Next, we define two threads,
thread2 to run the functions
count_up, respectively. We add
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_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
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
thread2and counts up to 5 starting at 0.
count_downfunction runs on
thread1counts 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
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
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!🎉