tech-sharing / 2026-04-15
从 Future::poll 到 tokio 的 work-stealing,一次讲清楚。
agenda.toml
// context
每条连接一根 OS 线程,栈 2–8MB。10 万连接 = 几百 GB RAM。
❌ 不现实epoll/kqueue + 回调地狱。快,但写起来痛苦且容易出 bug。
😩 callback hell看起来像同步代码,编译成状态机。一根线程跑几千任务。
✅ Rust 选这个deep-dive · 1 / 2
编译器把 async fn 变成一个实现了 Future trait 的匿名状态机。运行时只做一件事:反复 poll 它,直到返回 Ready。
pub trait Future { type Output; fn poll( self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll<Self::Output>; } // Poll::Pending → 挂起,等 waker 唤醒 // Poll::Ready(v) → 完成,产出 v
deep-dive · 2 / 2
Multi-thread runtime = N 个 worker,每个 worker 有自己的本地队列。空闲的 worker 会去别人队列里"偷"任务。
mini-runtime.rs · ~40 LOC
use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll, Wake, Waker}; struct Task(Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>); impl Wake for Task { fn wake(self: Arc<Self>) { QUEUE.lock().unwrap().push_back(self); } } fn block_on<F: Future<Output = ()> + Send + 'static>(fut: F) { spawn(fut); while let Some(task) = QUEUE.lock().unwrap().pop_front() { let waker = Waker::from(task.clone()); let mut cx = Context::from_waker(&waker); let mut fut = task.0.lock().unwrap(); let _ = fut.as_mut().poll(&mut cx); // 就是这一行 } }
// takeaways
编译成状态机,没有运行时虚表,没有 GC。
Future 不主动做事,运行时靠 waker 决定"什么时候再 poll"。
一行 std::fs::read 能让整个 worker 停摆。用 spawn_blocking。
延伸阅读:tokio.rs/blog/2019-10-scheduler · rust-lang.github.io/async-book
github.com/lewis · @lewis on slack