Concurrency in Python

By | January 18, 2024


In Python, there are several ways to implement concurrency, allowing you to execute multiple tasks concurrently to improve the performance of your code. Here are some commonly used methods:

Threading:

  • Python’s threading module allows you to create and manage threads. Threads are lighter-weight than processes, making them suitable for I/O-bound tasks. However, due to the Global Interpreter Lock (GIL), they are less effective for CPU-bound tasks.
  • Example:
import threading

def print_numbers():
    for i in range(5):
        print(f"Thread 1: {i}")

def print_letters():
    for letter in 'ABCDE':
        print(f"Thread 2: {letter}")

thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

Multiprocessing:

  • Python’s multiprocessing module allows you to create and manage processes. Unlike threads, processes have their own memory space and are not affected by the GIL, making them suitable for CPU-bound tasks.
  • Example:
import multiprocessing

def print_numbers():
    for i in range(5):
        print(f"Process 1: {i}")

def print_letters():
    for letter in 'ABCDE':
        print(f"Process 2: {letter}")

process1 = multiprocessing.Process(target=print_numbers)
process2 = multiprocessing.Process(target=print_letters)

process1.start()
process2.start()

process1.join()
process2.join()

Asyncio (asynchronous I/O):

  • The asyncio module provides a framework for writing asynchronous code using coroutines. It is suitable for I/O-bound tasks where waiting for external resources is a significant part of the program’s execution.
  • Example:
import asyncio

async def print_numbers():
    for i in range(5):
        print(f"Coroutine 1: {i}")
        await asyncio.sleep(1)

async def print_letters():
    for letter in 'ABCDE':
        print(f"Coroutine 2: {letter}")
        await asyncio.sleep(1)

asyncio.run(asyncio.gather(print_numbers(), print_letters()))

ThreadPoolExecutor and ProcessPoolExecutor:

  • The concurrent.futures module provides a high-level interface for asynchronously executing function calls. ThreadPoolExecutor and ProcessPoolExecutor allow you to create pools of threads or processes.
  • Example:
from concurrent.futures import ThreadPoolExecutor

def print_numbers():
    for i in range(5):
        print(f"Thread: {i}")

def print_letters():
    for letter in 'ABCDE':
        print(f"Thread: {letter}")

with ThreadPoolExecutor() as executor:
    executor.submit(print_numbers)
    executor.submit(print_letters)

Choose the concurrency approach that best fits your specific use case, considering factors such as the nature of your tasks (CPU-bound or I/O-bound), the GIL, and the complexity of managing concurrent code.

Leave a Reply

Your email address will not be published. Required fields are marked *