Python
threading
concurrency
programming
tutorial

How do I use threading in Python?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Threading in Python is a method of concurrent programming in which multiple threads are spawned by a process to perform tasks simultaneously. While Python’s Global Interpreter Lock (GIL) allows only one thread to execute at a time per interpreter, threading is valuable for I/O-bound tasks where waiting for input/output is a bottleneck. This article discusses how to use threading effectively in Python, providing code examples and highlighting key concepts essential to understanding and implementing threading.

Understanding Threads

In computer science, a thread is the smallest unit of processing that can be scheduled by an operating system. Threads run within a process and share the process's resources but execute independently. Python’s threading module allows for the creation and management of threads.

Key Features of Threads

  • Shared Memory: Threads within a process share the same memory space, facilitating inter-thread communication but also necessitating careful management to avoid race conditions.
  • Lighter: Threads are more lightweight than processes because they utilize the parent's memory space and resources.
  • Concurrency: While Python threads are subject to the GIL, they can still be useful for concurrent execution, especially for I/O-bound tasks.

Implementing Threading in Python

Python's built-in threading module provides a way to create and manage threads. Here are the fundamental steps to implement threading:

Basic Thread Creation

To start a new thread, you can utilize the Thread class in the threading module.

python
1import threading
2
3def print_hello():
4    print("Hello from thread!")
5
6# Create a thread
7thread = threading.Thread(target=print_hello)
8
9# Start the thread
10thread.start()
11
12# Wait for the thread to finish
13thread.join()

Subclassing Thread

You can also subclass the Thread class to create threads. This method provides greater control over the thread's behavior:

python
1class MyThread(threading.Thread):
2    def run(self):
3        print("Hello from a subclassed thread!")
4
5# Create and start the thread
6my_thread = MyThread()
7my_thread.start()
8
9# Wait for the thread to finish
10my_thread.join()

Thread Synchronization

Threads can run into issues like race conditions if access to shared data isn't managed. Python provides several synchronization primitives to manage access to shared data:

Locks

A lock can be acquired and released using the acquire() and release() methods:

python
1lock = threading.Lock()
2
3def thread_safe_function():
4    lock.acquire()
5    try:
6        # Critical section of code
7        print("This section is thread-safe")
8    finally:
9        lock.release()

RLock

An RLock, or reentrant lock, can be acquired multiple times by the same thread without causing a deadlock:

python
1rlock = threading.RLock()
2
3def thread_safe_reentrant():
4    rlock.acquire()
5    try:
6        # Critical section
7        rlock.acquire()
8        try:
9            # Nested critical section
10            print("Nested reentrant section")
11        finally:
12            rlock.release()
13    finally:
14        rlock.release()

Deadlock Avoidance

Deadlock can occur when two or more threads are blocked forever, waiting for each other to release resources. Strategies to avoid deadlock include using timeout with locks and ensuring consistent lock acquisition ordering.

Threading vs Multiprocessing

While threading is suited for I/O-bound tasks, for CPU-bound tasks, the multiprocessing module can be more appropriate as it bypasses the GIL ensuring multiple processes execute in parallel across multiple CPU cores.

Table: Differences Between Threading and Multiprocessing

FeatureThreadingMultiprocessing
MemoryShared memory resourcesSeparate memory space for each process
WeightLighter, runs within same processHeavier, separate process overhead
GILBound by GILBypassed, true parallelism on multi-core processors
Ideal ForI/O-bound tasksCPU-bound tasks

Conclusion

Threading in Python is a powerful tool when dealing with I/O-bound and high-latency operations. While Python's GIL imposes certain limitations, with proper synchronization and careful design, threading can significantly improve program concurrency and efficiency. Understanding when and how to use threading effectively, as contrasted with multiprocessing, can lead to substantial programming performance benefits.


Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.