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;
awaitnhườ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;defsync 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
- Concurrency vs parallel — deal vs do; coroutine phục vụ deal.
- Blocking vs non-blocking — chờ mà kẹt hay vẫn chạy việc khác; suspend = non-block khi nhả runner.
- Event loop — ai resume sau await; sync ôm loop thì không ai được resume.
- Unbounded in-flight · Backpressure — coroutine nhiều vẫn cần trần.
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.