Selector 是轮子,协程是车身——同一根棍子,天差地别的写法
// ========== 异步框架(回调版) ==========
function loadPage(userId) {
fetchUser(userId, function(user) { // ① 回调
fetchOrders(user.id, function(orders) { // ② 回调套回调
fetchRecommendations(user.id, function(recs) { // ③ 再套
renderPage(user, orders, recs); // ④ 真正的活
});
});
});
} // 回调地狱。每个异步操作都要包一个函数,逻辑被撕碎。
// ========== 异步框架(Promise 版) ==========
function loadPage(userId) {
return fetchUser(userId)
.then(user => fetchOrders(user.id)) // 链式,好一点
.then(orders => {
return fetchRecommendations(userId)
.then(recs => renderPage(user, orders, recs));
}); // 还是得拆成 then,user 变量传递麻烦
}
// ========== 协程版 ==========
suspend fun loadPage(userId: String) {
val user = fetchUser(userId) // ① 看起来阻塞
val orders = fetchOrders(user.id) // ② 同上
val recs = fetchRecommendations(userId) // ③ 同上
renderPage(user, orders, recs) // ④ 真正的活
} // 和同步代码一模一样。顺序写,顺序读。
协程让你用顺序的脑子写异步的活。你写的是故事,编译器帮你拍成分镜。
void loadData() {
User u = fetch(); // 卡住
show(u);
}
// OS 线程: 卡死,浪费
loadData 状态机:
case 0: 发起 fetch → 挂起
case 1: 拿到 u → show(u)
底层: epoll 监听 fd
OS 线程: 跑千千万万个协程
suspend(Kotlin)/ 什么都不标(Go、Java)、用作用域管好生命周期。你写的顺序阻塞代码 → 编译器切成状态机 → 堆上的续体对象 → 每次 I/O 挂起点,协程 suspended,线程跑别的协程 → epoll(那根棍子)在所有连接之间转,谁有数据叫醒谁 → 续体恢复,从下一个 case 继续 → 你看是阻塞,底下是非阻塞 + 状态机 + epoll。
协程 = 编译器替你写回调。