Python Multithreading vs Multiprocessing
The Chai & Masala Analogy

When we add threads to a Python script, we expect it to run faster. Most of the time, it runs at the exact same speed.
Textbooks and YouTube tutorials make Processes, Threads, and the GIL sound way more complicated than they actually are, so here is a simple summary using an Indian chai making example.
Key Nuance: Process vs. Thread
- Process: A Process is a kitchen. It has its own masala box (memory), its own resources, and its own space.
- Thread: A Thread is a chef working inside that kitchen.
Every kitchen (process) starts with one chef (the main thread).
A Process is a superset of a thread. A process is a container that holds one or more threads, so in a structural sense it is larger. A thread is the actual unit of execution that does the work to complete a process.
Multithreading (Concurrency)
Multithreading is about Concurrency, which means dealing with multiple tasks at once by switching between them quickly whenever one task is forced to wait.
Analogy
Suppose we hire 5 chefs to work in a single kitchen. Since they are in the same kitchen, they share the same masala set (same memory).
Suppose Chef A puts tea on the stove to boil. Until the tea boils, Chef A is just waiting. Instead of letting the kitchen sit idle, Chef B collects the masala for the tea while the water heats up, and Chef C grinds the ginger and elaichi. They aren't all using the stove at the exact same millisecond, they are just taking turns whenever someone has to wait on a slow step.
The Python Twist [Global Interpreter Lock]
The GIL is a mutex (lock) mechanism that allows only one thread to execute Python bytecode at a time, even on a multi-core CPU.
Think of the GIL as a single, magical saucepan. Even if you have 5 chefs (threads) in the kitchen, only the chef holding the magical pan is allowed to actively cook.
Why it exists
The default Python interpreter (CPython) uses reference counting for garbage collection. Every object in Python tracks how many places are referencing it. If two threads modify an object's reference count simultaneously, there will be a race condition and memory corruption. The GIL stops two chefs from grabbing the same masala spoon at the exact same instant.
When to use it [I/O-Bound Tasks]
Requesting data from a database, reading from a hard drive, or downloading an image are I/O-bound tasks. Because they work outside the CPU, they are much slower than our processor. If threads are not used here, our program freezes and does nothing until the slow task completes.
Now the question comes: Why doesn't the GIL block it?
As stated earlier, the GIL has a rule that "only one thread can execute Python code at a time." So why doesn't it block I/O tasks?
Because waiting for a network response does not use the CPU to execute Python bytecode. Whenever a thread hits an I/O wait (like a network request or time.sleep), Python releases the GIL until the data comes back.
In our kitchen, once Chef A puts the tea on the stove to simmer, Chef A puts down the magical pan while waiting for it to boil. Chef B immediately picks up the pan and starts the next order. Both chefs wait at the same time, so the script finishes in 2 seconds instead of 4:
import threading
import time
def fetch_data(endpoint):
print(f"Fetching from {endpoint}...")
time.sleep(2) # Simulating network wait (Thread drops the GIL here)
print(f"Done with {endpoint}!")
start = time.time()
# Takes ~2 seconds total, not 4, because both threads wait at the same time
thread1 = threading.Thread(target=fetch_data, args=("[https://vishalraghav.dev/projects](https://vishalraghav.dev/projects)",))
thread2 = threading.Thread(target=fetch_data, args=("[https://vishalraghav.dev/blogs](https://vishalraghav.dev/blogs)",))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(f"Finished in {round(time.time() - start, 2)}s")
Multiprocessing (Parallelism)
Multiprocessing is about Parallelism, doing multiple things at the exact same time by utilizing multiple CPU cores. Here each process gets its own Python interpreter and isolated memory space, which completely bypasses the GIL constraint.
Analogy
Here we won't go for 5 chefs in 1 kitchen. Instead, we go for 5 separate kitchens, and each kitchen gets its own ingredients, masala box, and appliances. They can all cook at the exact same time because they don't share a single magical GIL pan. The only catch is that running 5 separate kitchens takes up more memory (RAM).
When to use it [CPU-Bound Tasks]
Use multiprocessing whenever there is heavy processing, mathematical computation, rendering, or ML tasks where the CPU never stops to wait.
import multiprocessing
import time
def crunch_numbers(number):
print(f"Crunching batch {number}...")
# Simulating heavy CPU math
result = sum(i * i for i in range(10_000_000))
print(f"Done batch {number}!")
# Utilizes multiple CPU cores simultaneously
if __name__ == "__main__":
start = time.time()
process1 = multiprocessing.Process(target=crunch_numbers, args=(1,))
process2 = multiprocessing.Process(target=crunch_numbers, args=(2,))
process1.start()
process2.start()
process1.join()
process2.join()
print(f"Finished in {round(time.time() - start, 2)}s")
Free-Threaded Python (No-GIL Mode in Python 3.13 / 3.14+)
With recent Python updates (Python 3.13 and 3.14+), the GIL is now optional through the free-threaded build (python3.13t / python3.14t).
How does the kitchen work without the GIL?
If you remove the GIL so all 5 chefs can cook in the same kitchen at once, why don't they corrupt the masala box?
Instead of putting one giant lock on the whole kitchen (the GIL), Python puts small individual locks on the masala jars themselves. Now all 5 chefs in a single kitchen can work in parallel across multiple CPU cores. They only pause for a split microsecond if two chefs reach for the exact same elaichi jar at the same time.
Opening and closing those small locks on individual jars adds a tiny bit of overhead to single-threaded code, but heavy multi-threaded code gets a huge speedup without needing separate kitchens.
Quick Summary
| Feature | Multithreading | Multiprocessing | No-GIL Mode (3.13t / 3.14t) |
|---|---|---|---|
| Kitchen Setup | 1 Kitchen, 5 Chefs, 1 Pan | 5 Separate Kitchens | 1 Kitchen, 5 Chefs, Locks on Jars |
| Memory (Masala) | Shared (Low RAM) | Isolated (High RAM) | Shared (Low RAM) |
| Best Used For | I/O-Bound (APIs, DB, Disk) | CPU-Bound (Math, ML) | Multi-core CPU + Shared Memory |
About the author
- I am a learner and an explorer sharing my journey here on Hashnode. Please reach out if you like my work let's have a chat about it
Comments
Join the discussionI think it actually covers the multi threading part with dun concept of tea which is great Its a great read

