avatarPravash

Summary

The article distinguishes between Python's concurrency, threading, and parallelism, explaining their use cases and providing practical implementation examples.

Abstract

The article "Python: Parallelism Vs Concurrency Vs Threading" delves into the nuances of managing multiple tasks in programming, drawing parallels with a café's operations to clarify complex concepts. It defines concurrency as the ability to manage multiple tasks efficiently by switching between them, similar to a single barista handling several orders. Threading is likened to multiple clones of a barista, each handling a specific task but sharing resources, which can lead to bottlenecks. Parallelism, on the other hand, is akin to a large café with multiple baristas, each with their own resources, allowing for true simultaneous execution of tasks. The author emphasizes the importance of choosing the right approach—concurrency for I/O-bound tasks, threading for tasks that share memory but are not CPU-intensive, and parallelism for CPU-bound tasks requiring simultaneous execution—to optimize performance and resource utilization in Python projects. Practical examples using asyncio, threading, and multiprocessing libraries are provided to illustrate these concepts.

Opinions

  • The author believes that understanding the differences between concurrency, threading, and parallelism is crucial for optimizing performance in Python programs.
  • The use of café scenarios to explain technical concepts is an effective way to make them more relatable and understandable.
  • The article suggests that asyncio is best for I/O-bound tasks, implying that it is not suitable for CPU-bound tasks.
  • The author conveys that while threading can speed up task execution, it may not always lead to true parallelism due to shared resources and the Global Interpreter Lock (GIL) in CPython.
  • Multiprocessing is presented as the solution for CPU-bound tasks that need to run in parallel, leveraging multiple CPU cores.
  • The author's choice of examples and analogies indicates a preference for clear, real-world explanations to demystify complex programming paradigms.

Python: Parallelism Vs Concurrency Vs Threading

Unraveling the Art of Multitasking in Programming

Intro:

I believe understanding Concurrency, Threading, and Parallelism is essential, if you’re managing multiple tasks, speeding up processes, or ensuring independent workflows. These concepts might seem similar at first glance, but each has distinct roles in optimizing performance, much like how a café manages its staff and equipment.

In this article I will dive into these paradigms using relatable café scenarios to make these technical concepts accessible. I will also explore practical Python implementations using powerful libraries like asyncio, threading, and multiprocessing. Whether you're managing a single thread or leveraging multiple cores, you'll gain a clearer understanding of how to apply these concepts effectively in your projects.

Let's get brewing!

Concurrency 🕒:

Imagine you’re a single-barista café. You start brewing coffee for one order, and while it brews, you take another customer’s order, start steaming milk, then quickly begin assembling a sandwich for yet another customer.

You’re making progress on each order by constantly switching between tasks, ensuring everything gets done in an efficient flow without doing them all at once.

➡️ How to implement in Python:

import asyncio


async def task(name, delay):
    print(f"{name} started")
    await asyncio.sleep(delay)
    print(f"{name} completed")


async def main():
    task1 = asyncio.create_task(task("Task1", 2))
    task2 = asyncio.create_task(task("Task2", 3))

    print("Tasks are running concurrently...")

    await task1
    await task2

    print("Both tasks are completed")

asyncio.run(main())

Concurrency allows you to manage multiple tasks efficiently by switching between them, using packages like asyncio to make progress on all tasks, but not necessarily doing them at the same time.

Threading 🧵:

Imagine you’re running a busy coffee shop with several clones of yourself, each focused on a specific task. One clone brews coffee, another prepares pastries, and a third makes smoothies. Each clone (or thread) works independently on their assigned task, but they all share the same coffee shop space and equipment.

This setup speeds things up, but if there’s only one blender or coffee machine, they might still have to wait for each other occasionally, creating minor bottlenecks.

➡️ How to implement in Python:

import threading
import time


def task(name, delay):
    print(f"{name} started")
    time.sleep(delay)
    print(f"{name} completed")


if __name__ == '__main__':
    # Create threads
    thread1 = threading.Thread(target=task, args=('Thread1', 2))
    thread2 = threading.Thread(target=task, args=('Thread1', 2))

    print("Threads are running concurrently...")

    # start process
    thread1.start()
    thread2.start()

    # wait for process to complete
    thread1.join()
    thread2.join()

    print("Both Threads completed")

Threading runs different parts of a program as separate threads that share the same memory and resources. The tasks run concurrently, but they may not truly be running at the same exact time due to CPU switching between threads.

Parallelism 🏎️:

Imagine a large café with multiple baristas, each working in their own fully-equipped station. One barista is focused on making coffee, another is blending smoothies, and a third is preparing sandwiches — all in separate areas with their own machines and tools. Since each barista has their own resources, they can work independently and serve customers at the same time without any waiting or interference.

This setup allows orders to be completed faster, with each barista working simultaneously in parallel.

➡️ How to implement in Python:

import multiprocessing
import time


def task(name, delay):
    print(f"{name} started")
    time.sleep(delay)
    print(f"{name} completed")


if __name__ == '__main__':
    process1 = multiprocessing.Process(target=task, args=("Process 1", 2))
    process2 = multiprocessing.Process(target=task, args=("Process 2", 3))

    print("Processes are running in parallel...")

    # start process
    process1.start()
    process2.start()

    # wait for process to complete
    process1.join()
    process2.join()

    print("Both processes completed")

Parallelism involves running multiple tasks literally at the same time using multiple CPU cores. Packages like multiprocessing create separate processes that run independently on different cores of the processor, so true parallel execution is possible.

🧠 Now you might be thinking that — Threading sounds like Concurrency, but there is a difference.

➡️ Concurrency: Multitasking chef switching between different tasks to make progress on all of them.

➡️ Threading: Multiple chefs (threads) working simultaneously but sharing the same kitchen resources.

Concurrency is about managing tasks and switching between them, while Threading is one way to achieve concurrency by breaking down tasks into multiple threads.

Conclusion:

The choice between concurrency, threading, and parallelism depends on the nature of tasks:

  • Use concurrency (asyncio) for I/O-bound tasks with minimal CPU load.
  • Use threading for concurrent tasks that share memory and are not CPU-intensive.
  • Use parallelism (multiprocessing) for CPU-bound tasks that require true simultaneous execution.

Understanding the differences ensures efficient resource utilization and optimal program performance.

Python
Threading
Concurrency
Python Programming
Python3
Recommended from ReadMedium