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.






