Concurrency and the GIL
Introduction
Welcome to the first lesson of Python Concurrency & Async I/O! You've made tremendous progress through the previous courses in this learning path. You've mastered the Python data model, built sophisticated class machinery with descriptors and metaclasses, and explored functional patterns, including composable error handling with Result types. Now we're shifting gears to explore one of Python's most powerful yet misunderstood domains: concurrent and asynchronous programming.
Throughout this course, we'll explore Python's different concurrency models and learn to write high-performance, robust asynchronous code. We'll begin by understanding the Global Interpreter Lock and when to use threads versus processes. We'll then dive deep into the asyncio event loop, learning to build producer-consumer pipelines, manage task groups with structured concurrency, and implement backpressure and retry logic for resilient systems. This journey will equip you with the tools to build applications that handle multiple tasks efficiently, whether you're processing data in parallel, handling network requests, or orchestrating complex workflows.
Today's lesson focuses on the fundamentals: differentiating CPU-bound and I/O-bound workloads and choosing the right concurrency tool. We'll benchmark threads versus processes to observe the Global Interpreter Lock's effect on CPU-bound code and see threads shine for I/O-bound tasks, while processes deliver true parallelism for CPU-bound work. By the end of this lesson, you'll understand when to reach for each tool and how to measure their impact on your specific workloads.
Understanding CPU-Bound and I/O-Bound Workloads
Before we explore Python's concurrency tools, we need to understand the two fundamental types of workloads: CPU-bound and I/O-bound. This distinction determines which concurrency approach will be effective for your particular problem.
CPU-bound workloads spend most of their time performing calculations and manipulating data in memory. Examples include image processing, mathematical computations, data compression, and cryptographic operations. These tasks keep the processor busy; adding more CPU cores allows them to run faster because the work can be distributed across cores. The bottleneck is the CPU itself.
I/O-bound workloads spend most of their time waiting for external resources: reading from disk, making network requests, or querying databases. The CPU is mostly idle during these operations, waiting for data to arrive. Examples include web scraping, API clients, and file processing pipelines. Adding more CPU cores doesn't help much because the bottleneck is the external resource, not the computation. What matters is the ability to work on other tasks while waiting.
This distinction matters because Python's concurrency tools behave very differently depending on the workload type. The Global Interpreter Lock affects CPU-bound tasks differently than I/O-bound tasks, and choosing the wrong tool can make your code slower rather than faster.
