Python
Threads
Multithreading
Concurrent Programming
Python Programming

Creating Threads in python

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Overview of Threads in Python

Threads in Python allow for the execution of multiple pieces of code concurrently. This can be especially beneficial in scenarios where you need to manage multiple tasks simultaneously without blocking the main program's execution. Python's threading module provides a simple and efficient way to work with threads.

Understanding the threading Module

Python's threading module is the primary way to handle threading. It defines a Thread class that you can inherit to create and manage threads. Below are some of the key components of the module:

  • Thread: The core class used for creating and managing threads.
  • Lock: A synchronization primitive that can be used to block threads.
  • RLock: A reentrant lock that can be acquired multiple times by the same thread.
  • Semaphore: Restricts access to a resource.

Creating a Thread

To create a thread, you generally extend the Thread class or pass a callable object (i.e., a function) to the Thread constructor. Here's a simple example of how to create and run a thread using a function:

python
1import threading
2
3def print_numbers():
4    for i in range(5):
5        print(i)
6
7number_thread = threading.Thread(target=print_numbers)
8number_thread.start()
9number_thread.join()

Creating a Thread by Extending Thread Class

You can also create threads by creating a class that extends the Thread class and overrides the run() method:

python
1import threading
2
3class PrintNumbers(threading.Thread):
4    def run(self):
5        for i in range(5):
6            print(i)
7
8number_thread = PrintNumbers()
9number_thread.start()
10number_thread.join()

Synchronizing Threads

When using threads, proper synchronization is critical to ensure that shared resources are not accessed simultaneously by multiple threads, which can lead to race conditions.

Locks

A Lock can be used to block the execution of other threads until the lock is released:

python
1import threading
2
3lock = threading.Lock()
4
5def synchronized_task():
6    lock.acquire()
7    try:
8        # Critical section
9        print(f'Thread {threading.current_thread().name} is working')
10    finally:
11        lock.release()
12
13threads = [threading.Thread(target=synchronized_task) for _ in range(3)]
14for t in threads:
15    t.start()
16for t in threads:
17    t.join()

RLocks

An RLock can be acquired multiple times by the same thread:

python
1import threading
2
3r_lock = threading.RLock()
4
5def task_with_relock():
6    with r_lock:
7        print(f'Thread {threading.current_thread().name} acquired the lock.')
8        with r_lock:
9            print(f'Thread {threading.current_thread().name} reacquired the lock.')
10
11threads = [threading.Thread(target=task_with_relock) for _ in range(3)]
12for t in threads:
13    t.start()
14for t in threads:
15    t.join()

Daemon Threads

A daemon thread runs in the background and is typically used for tasks that are not critical. A key aspect of daemon threads is that they automatically terminate when the main program exits:

python
1import threading
2import time
3
4def background_task():
5    while True:
6        print('Daemon thread running')
7        time.sleep(1)
8
9thread = threading.Thread(target=background_task)
10thread.daemon = True
11thread.start()
12
13time.sleep(5)
14print('Main program exits')

Challenges in Python Threading

Python's threading is limited by the Global Interpreter Lock (GIL), which allows only one thread to execute Python bytecode at a time. Because of the GIL, threading may not lead to a performance increase in CPU-bound tasks. Instead, it's more beneficial for I/O-bound tasks where the program spends time waiting for external resources.

Alternatives to Threading

If your program is CPU-bound and you need true parallel execution, consider using the multiprocessing module:

python
1import multiprocessing
2
3def compute_square(n):
4    return n * n
5
6with multiprocessing.Pool() as pool:
7    results = pool.map(compute_square, [1, 2, 3, 4])
8print(results)

Summary Table

FeatureUse CaseCode Example or Function
ThreadStart a new threadthreading.Thread()
LockSynchronize access to a resourcethreading.Lock()
RLockAllow a thread to re-acquire locksthreading.RLock()
Daemon ThreadBackground tasks, not blocking exitthread.daemon = True
Global Interpreter Lock (GIL)Only one thread’s bytecode executes at a time due to GIL--
MultiprocessingTrue parallelism for CPU-bound tasksmultiprocessing.Pool()

Conclusion

Threads in Python provide a way to perform operations concurrently, maximizing the efficiency of I/O-bound applications. It's crucial to manage synchronization properly to avoid race conditions. While threading is powerful, understanding when to use it versus alternatives like multiprocessing can significantly impact the performance and behavior of your program.


Course illustration
Course illustration

All Rights Reserved.