Bài concurrency vs parallel nói dealing with nhiều việc cùng lúc. Blocking vs non-blocking nói khi chờ thì unit có kẹt hay không.

Coroutine trả lời tầng “cơ chế”: làm sao code đứng giữa chừng, nhường chỗ, rồi chạy tiếp đúng chỗ đó?

Coroutine ≈ unit thực thi do app/runtime quản
  · có thể suspend (tạm dừng, nhả runner)
  · có thể resume (chạy tiếp từ điểm dừng)
  · thường cooperative: tự nhường, không bị OS cắt giữa instruction

Demo timeline (1 runner, nhiều coroutine suspend/resume): coroutine suspend / resume.

Suspend / resume — hình dung nhanh

async function handle(req) {
  const user = await db.get(req.userId)  // ① suspend
  // … runner đi deal request khác …
  // data về → ② resume đúng dòng sau await
  return render(user)
}

Ở ①, coroutine không chết — nó cất state (biến local, chỗ đang đứng) rồi nhả thread/event-loop. Ở ②, runtime gắn lại state và chạy tiếp. Đó là cách dealing with nhiều request trên ít thread: phần lớn thời gian mỗi việc đang suspend chờ I/O, không ôm runner.

Nhà bếp (cùng tinh thần series):

  • Thread block: đầu bếp đứng nhìn nồi — một người một nồi đến khi sôi.
  • Coroutine: hẹn giờ, ghim “nồi A phút 3”, đi deal nồi B; chuông reo → resume A đúng chỗ đang nấu.
  • Parallel: thêm đầu bếp — đó là doing, không phải định nghĩa coroutine.

Thread vs coroutine

Thread (OS)
  · preemptive — OS cắt / schedule
  · stack ~MB, tạo đắt, context switch đắt hơn
  · block I/O: thread ngủ, vẫn “chiếm” slot pool

Coroutine (app/runtime)
  · cooperative — suspend tại điểm rõ (await, yield…)
  · state nhẹ hơn thread đầy đủ (stackless / stack nhỏ tùy runtime)
  · chờ I/O: suspend → runner đi deal việc khác

Không phải “coroutine thay thread mọi nơi”. CPU nặng, syscalls, driver… vẫn cần thread (hoặc process). Coroutine mạnh khi bài toán là nhiều việc dang dở + chờ — đúng chỗ series gọi là concurrency (deal).

Go hơi “lai”: goroutine do runtime schedule, map lên OS thread; bạn viết như coroutine rẻ, scheduler có thể preemptive theo safepoint. Ý thực dụng vẫn: unit deal rẻ, không phải “mỗi request một OS thread 2MB”.

async / await = cách viết coroutine

Trong blocking vs non-blocking:

async / Promise / Future  =  cách viết code suspend được
non-blocking              =  khi chờ, unit không bị giữ

Thêm một dòng:

coroutine                 =  unit mang state suspend/resume
async function + await    =  syntax cho coroutine (JS, Python, C#, …)
generator yield           =  anh em: suspend theo tay, không chỉ I/O
  • async function + await — compiler/runtime biến thành state machine (stackless coroutine).
  • Promise chỉ là handle “sẽ xong”; coroutine/task là ai đang chạy / đang suspend.
  • API tên async_* mà bên trong ôm thread đến khi xong → có “hình” coroutine, không có “chất” non-block trên unit đó.

Ai resume? — event loop / scheduler

Coroutine không tự sống một mình. Cần runner:

  • Node / browser: event loop — I/O xong, timer fire → queue callback/microtask → resume continuation sau await.
  • asyncio: loop + Task; await nhường loop.
  • Go: runtime scheduler, M:N goroutine → thread.
  • Kotlin: coroutine + dispatcher (Main, IO, Default…).

Hệ quả thực dụng (đã nói ở blocking): CPU spin / sync I/O trên loop = không ai resume việc khác → concurrency trên giấy, serial trên thực tế. Coroutine chỉ giúp khi điểm suspend thật sự nhả runner.

Không free — mỗi coroutine in-flight vẫn tốn

Rẻ hơn thread ≠ vô hạn. Mỗi task suspend vẫn giữ:

  • closure / frame (biến local, buffer đang build),
  • socket, request object, timeout handle,
  • chỗ trong pool DB / connection.

Bắn 100k coroutine/Promise cùng lúc → OOM hoặc thrash GC. Đó là unbounded in-flight. Giới hạn concurrent start: pool, backpressure — vẫn đúng khi unit là coroutine thay vì thread.

// “rẻ” nhưng không bound → vẫn chết
await Promise.all(hugeList.map(job))  // N coroutine in-flight

// bound: tối đa K đang bay
await pool.map(hugeList, job, { concurrency: K })

Runtime (một dòng mỗi cái)

  • JS: async/await + Promise; 1 thread JS + libuv — coroutine stackless trên paper, block sync = kẹt cả nhà.
  • Python asyncio: async def + loop; def sync trong async path = block loop.
  • Go: go func() — goroutine rẻ; channel để phối hợp; parallel khi GOMAXPROCS > 1 + có việc CPU.
  • Kotlin: suspend fun + structured concurrency (scope) — cancel lan theo cây coroutine.

Thực dụng

  • I/O-bound, nhiều connection: coroutine / async là model deal mặc định (Node, asyncio, netty-style, Go server).
  • CPU-bound: suspend không rút wall — cần parallel doing (worker, process, pod).
  • Đếm “đang bay” và đặt trần — coroutine rẻ dễ khiến người ta quên bound.
  • Phân biệt: cú pháp async (có suspend point) vs non-block thật (chờ có nhả runner không).

Đọc tiếp

Một câu để nhớ

Coroutine = unit suspend / resume do runtime quản, để dealing with nhiều việc trên ít thread.
Thread = unit OS schedule; async/await = cách viết coroutine.

Suspend khi chờ → runner đi deal việc khác → resume đúng chỗ. Không thay parallel khi bài là CPU; không miễn bound in-flight. Hiểu coroutine là hiểu vì sao non-block + async sống được trên một event loop.