Asynchronous I/O and Coroutines
In previous chapters, we built the foundation for concurrent programs using threads, mutexes, atomics, and futures. However, when we face "I/O-bound" scenarios—such as a network server handling thousands of connections simultaneously—the traditional "one thread per connection" model reveals serious resource waste. Threads consume memory and scheduling resources while simply waiting for I/O. We need a lighter-weight way to express "go do something else, and come back when the I/O is done."
In this chapter, we start with the evolution of asynchronous programming paradigms, comparing the motivations and pain points of callbacks, future chains, and coroutines to understand why coroutines are considered the "right way to do async." We then dive into the internal mechanisms of C++20 coroutines—the compiler's state machine transformation of coroutine functions, the allocation and destruction of coroutine frames, and the lifetime management of coroutine_handle—and implement a complete generator from scratch to tie all the concepts together. Next, we turn our attention to the two major customization extension points of coroutines (promise_type and awaitables), connecting coroutines with the operating system's I/O multiplexing mechanisms to build a coroutine-driven event loop. Finally, we use a complete coroutine Echo Server to tie all the knowledge points together in practice.