Queues vs Threads vs Async: The Guide Every Python Developer Should Know
Most Python developers know about concurrency but very few actually know when to use:
And this confusion creates:
So today, we’re fixing that.
Here’s the backend developer’s guide to picking the right concurrency model every time.
01 Threads: Best for I/O, Worst for CPU
Use threads when:
Avoid threads when:
Example (ThreadPoolExecutor)
from concurrent.futures import ThreadPoolExecutor
import requests
from typing import List
def fetch(url: str) -> str:
return requests.get(url).text
urls: List[str] = ["https://api.github.com", "https://google.com"]
with ThreadPoolExecutor(max_workers=10) as executor:
responses = list(executor.map(fetch, urls))
02 AsyncIO: Perfect for High-Concurrency I/O
Async is not multithreading. It’s a single thread doing smart task switching.
Use Async when:
Avoid Async when:
Example (AsyncIO)
import aiohttp
import asyncio
from typing import List
async def fetch(session: aiohttp.ClientSession, url: str) -> str:
async with session.get(url) as response:
return await response.text()
async def main() -> None:
urls: List[str] = ["https://api.github.com", "https://google.com"]
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
03 Multiprocessing: The Only Real Solution for CPU Work
Want parallelism? You’ll never get it with threads (GIL says no).
Use Processes when:
Avoid when:
Example (ProcessPoolExecutor)
from concurrent.futures import ProcessPoolExecutor
from typing import List
def heavy_compute(x: int) -> int:
return x * x * x
numbers: List[int] = list(range(10000))
with ProcessPoolExecutor() as executor:
results = list(executor.map(heavy_compute, numbers))
04 Background Queues: The Real Production Way
Your API shouldn’t process heavy tasks directly.
That's where queues come in:
Use Queues when:
Example (Celery Task)
from celery import Celery
app = Celery("worker", broker="redis://localhost:6379")
@app.task
def generate_report(user_id: int) -> str:
return f"Report created for {user_id}"
Final Takeaway
Professional developers don’t guess. They pick the concurrency model that matches the workload.
Threads = simple I/O
Async = high concurrency
Processes = CPU bound
Queues = real production architecture
Master this, and your Python systems automatically jump into the “senior-level” category.
Good 👍