Skip to main content

1. Concurrency vs. Parallelism

To understand asynchronous programming, we must distinguish between concurrency and parallelism:
  • Concurrency: Handling multiple tasks during overlapping periods of time. Tasks do not necessarily execute at the same time. Concurrency means multiple tasks are in progress.
  • Parallelism: Executing multiple tasks at the exact same time, usually on different CPU cores (e.g., rendering video, matrix multiplication).
Parallelism is a type of concurrency, but concurrent tasks do not have to execute simultaneously.

2. I/O-Bound vs. CPU-Bound Tasks

Understanding the nature of your workload helps determine the best concurrency model.

I/O-Bound Tasks

The program spends most of its time waiting for external operations to complete.
  • Examples: API requests, database queries, network requests, disk file operations.
  • Solution:
    • If an asynchronous library is available: Asyncio
    • If only synchronous/blocking libraries are available: Multithreading

CPU-Bound Tasks

The program spends most of its time calculating and performing computations on the CPU.
  • Examples: Large mathematical calculations, image/video processing, machine learning inference, cryptographic operations.
  • Solution: Multiprocessing

3. Concurrency Models in Python

Python provides three primary ways to handle concurrent execution: Asyncio, Multithreading, and Multiprocessing.

A. Asyncio (Asynchronous I/O)

asyncio provides concurrency using an event loop, typically running in a single thread. When a task waits for I/O, it pauses and yields control back to the event loop.
  • Best for: Async API calls, async database queries, WebSockets, network operations, and handling many concurrent I/O operations.
Note: Use asyncio when the I/O library supports asynchronous operations.

B. Multithreading

Multithreading uses multiple threads within a single process. Because of Python’s Global Interpreter Lock (GIL), only one thread can execute Python bytecodes at a time, making threads ideal for waiting on blocking network or file calls rather than CPU calculations.
  • start() → Starts the thread’s activity.
  • join() → Blocks the calling thread until the thread whose join() method is called terminates.
  • Best for: Blocking API calls, synchronous libraries, blocking file/network operations, and legacy code without async support.
Note: Use threads when the operation is I/O-bound but the library is synchronous/blocking.

C. Multiprocessing

Multiprocessing creates separate processes, each with its own Python interpreter and memory space. This completely bypasses the GIL, allowing true parallel execution across multiple CPU cores.
  • Best for: Heavy calculations, image/video processing, CPU-intensive data manipulation, and computational algorithms.
Note: Use multiprocessing when the work requires significant CPU computation.

4. Async & Await Declarations

Python’s asyncio framework uses the keywords async def and await to write asynchronous code.

Coroutines

Declaring a function with async def creates a coroutine. Calling a coroutine does not run it; it returns a coroutine object. To execute it, you must await it.

5. The Event Loop & Task Scheduling

The Event Loop is the engine that runs asynchronous applications. It manages the execution of different tasks:
  1. It runs a task until the task hits an await expression (blocking I/O).
  2. While that task waits for I/O (e.g., waiting for database results), the loop pauses it and switches to run another ready task.
  3. Once the I/O operation finishes, the event loop resumes the original task.

Running Tasks Concurrently

To run multiple operations concurrently instead of sequentially, you can group them into asyncio.create_task() or use asyncio.gather().

6. Why FastAPI Uses Async

FastAPI is built on ASGI (Asynchronous Server Gateway Interface) and supports native async def route handlers. When a client sends a request to an async def endpoint that performs a database query or external API fetch, FastAPI yields control back to the event loop. The event loop can process other incoming requests in the meantime, resulting in massive throughput gains.

7. Quick Decision Guide

Use this flowchart and table to quickly decide which concurrency model fits your task:

Final Summary

  • Asyncio → Asynchronous I/O
  • Multithreading → Blocking/Synchronous I/O
  • Multiprocessing → CPU-bound work
  • Concurrency = Multiple tasks are in progress (overlapping execution).
  • Parallelism = Multiple tasks execute simultaneously (requires multiple CPU cores).
Key Takeaway: Parallelism is a form of concurrency, but concurrency does not necessarily mean parallelism.

Practice & Exercises

To reinforce what you’ve learned in this section (async/await declarations, tasks, event loops, and concurrency), practice with these interactive notebooks:

Follow-Along Practice

Practice defining async coroutines, working with non-blocking sleeps, understanding task scheduling, and implementing concurrent operations using asyncio.gather.💻 VS Code | 🚀 Colab | 📥 Download

Practice Exercises

Test your knowledge with hands-on exercises including simulated async file downloaders and concurrent batch file managers.💻 VS Code | 🚀 Colab | 📥 Download