本文来自知乎:https://zhuanlan.zhihu.com/p/137353103
作者:赖智超
smol是一个精简高效的异步运行时,包含有Executor,Reactor和Timer的实现。本文分析其中的Executor部分,借助于async_task(之前的文章已经详细分析过了)打下的基础,executor的实现非常清晰简洁,整个代码几个小时就能分析完毕。smol实现的executor有三类:
thread-local:用于执行!Send的task,由Task::local创建;
work-stealing: 多线程带工作窃取,执行Task::spawn创建的Task;
blocking executor: 线程池执行带阻塞的task,由Task::blocking创建;
smol::run会执行参数提供的future至结束,同时充当工作线程的角色执行thread-local,work-stealing中spawn出来的task以及推进reactor中的IO事件和定时器。另外也提供了smol::block_on方法,来执行单个future。下面分别分析各个executor的实现细节。
smol整体结构图
![](https://i-blog.csdnimg.cn/blog_migrate/a541ff6bbff16b39bf3ff8916c069a33.png)
Thread Local Executor
该Executor的特点是spawn出来的task和spawn调用所在的线程绑定,整个task从创建到执行到销毁都不会脱离该线程,因此可以用于!Send的Future。
结构定义
为了减少跨线程同步开销,ThreadLocalExecutor采用了并发和非并发两个队列:当其他线程唤醒task时,将task压入并发队列里;当本地线程要spawn新的task或者唤醒task时,压入非并发队列里。结构定义如下:
pub(crate)struct ThreadLocalExecutor{
// 非并发的主任务队列
queue: RefCell<VecDeque<Runnable>>,
// 当其他线程唤醒task时,放入该队列,支持并发调用
injector: Arc<SegQueue<Runnable>>,
// 用于通知executor线程,这样如果其阻塞在epoll上时可以立马被唤醒
event: IoEvent,
}
ThreadLocalExecutor::new
进行字段的初始化
pubfn new()-> ThreadLocalExecutor{
ThreadLocalExecutor{
queue: RefCell::new(VecDeque::new()),
injector: Arc::new(SegQueue::new()),
event: IoEvent::new().expect("cannot create an `IoEvent`"),
}
}
ThreadLocalExecutor::enter
executor嵌套(即在executor内部又创建executor),容易引发丢失通知导致死锁等问题.为了检测这种情况,通常的做法是设置一个线程局部变量,在进入executor前设置该变量,这样就可以检测嵌套的情况。enter函数接收一个闭包,在调用该闭包前将executor设置进thread local中,再执行闭包,调用结束时将thread local变量恢复。
scoped_thread_local!{
staticEXECUTOR: ThreadLocalExecutor
}
pubfn enter<T>(&self,f: implFnOnce()-> T)-> T{
// 已经设置了表示有嵌套
ifEXECUTOR.is_set(){
panic!("cannot run an executor inside another executor");
}
EXECUTOR.set(self,f)
}
ThreadLocalExecutor::spawn
spawn用于创建并调度task,其关键是记住当前spawn线程的id,当task唤醒的时候,拿唤醒的线程id和spawn线程id进行对比,如果相等则压入主队列,不相等则压入并发队列。
pubfn spawn<T: 'static>(future: implFuture<Output=T>+'static)-> Task<T>{
if!EXECUTOR.is_set(){
panic!("cannot spawn a thread-local task if not inside an executor");
}
EXECUTOR.with(|ex|{
// 这里使用弱引用是因为:Injector队列存有task,而task的waker(包含下面
// 的schedule闭包)含有injector的引用,这样可以避免循环引用。
letinjector=Arc::downgrade(&ex.injector);
leteven