serversim
LearnSimulatorTemplatesPricing
…
…
Open menu
Learn↗Simulator↗Templates↗Pricing↗
My DocumentsDocs
Contents/Chapter 1212 / 16

Coroutines / async-await — callbacks in sync clothing

If the speed came at the cost of readable code, must you give one of them up? Keep the way the work is done and rewrite only the log, so it reads top to bottom again.

See this first in FoundationsWhen does an event loop win, and when does it lose?→

Callbacks work but nest badly: query, then query again with the result, then undo on failure, and error handling scatters. Coroutines fix this with syntax — the execution model stays the event loop, only the shape of the code returns to looking synchronous.

① What await does for you

At an await point exactly what chapter 11 did happens: register a continuation and return. The difference is that the compiler or runtime writes that for you, and execution resumes at the same spot when the IO completes.

② Suspend and resume

An ordinary function runs to its return. A coroutine can pause mid-way and continue later from that point, with its locals and position kept somewhere — the same idea as chapter 6's context saving, but handled far more cheaply by the language runtime.

③ So how many threads?

Few. A coroutine is not a stack but roughly "a paused position plus some locals", so tens of thousands of coroutines cost incomparably less memory than tens of thousands of threads.

④ Different names, same idea

async/await in JavaScript and Python, suspend functions in Kotlin, goroutines in Go. Notice the animation is identical to chapter 11 — the execution model did not change, only the syntax.

⑤ Work coroutines are bad at

Coroutines are not a cure-all. Three cases are actively worse. First, long CPU work: with no suspension point, no other coroutine can get in. Second, calling a blocking library. Third, purely sequential logic with nothing to overlap. There you gain nothing and pay in broken stack traces and harder debugging.

⑥ The function colour problem

An async function can only be awaited naturally from another async function, so turning one place asynchronous spreads through the whole call chain. This is the function colour problem. It is precisely why virtual threads drew attention — they deliver the same effect without colouring your code.

Handler (coroutine) — identical execution to chapter 11
1async handle(req) {
2 id = parse(req)
3 user = await db.query(id)await = register + return, resumes here
4 return toJson(user)
5}
Speed

Try it in the simulator

  1. 1.Select WebSocket Server (WebFlux) in the 'WebSocket Chat' preset and observe event-loop settings and message-processing metrics. This simulation models message load, not the lifetime of real WebSocket connections.
  2. 2.Here the client RPS counts messages and write operations per second. Change it and observe CPU, throughput and latency. It is not a count of concurrent connections.
  3. 3.Tomcat also supports NIO and WebSocket, so an open connection does not continuously occupy its own platform thread. Thread demand depends on active handlers and blocking calls. As with this chapter’s coroutines, distinguish waiting state from the workers actually executing.
Try it in the simulator →

Terms in this chapter

coroutine
A function that can pause and later resume where it left off.
suspend / resume
Pausing and continuing; the thread does other work meanwhile.
async / await
The common syntax; await marks a point where the function may pause.
callback hell
Deeply nested callbacks with tangled control flow and error handling.
function colour
The way async spreads through a call chain, making incremental adoption hard.
broken stack traces
Async code loses the call path of an exception; it bills you during incidents.

Why it evolved this way

People reason far better about top-to-bottom code. If performance demands unreadable code, teams ship bugs — so languages adopted coroutines: event-loop performance with synchronous readability.

Reads like sync, runs like an event loop. Sugar, not magic.

The remaining problem

One weakness remains: the loop assumes everyone yields quickly. What if code does not? →

← Previous

Chapter 11. Server method ② — the event loop

Next →

Chapter 13. Trap ① — one CPU-bound task freezes the loop