Rust Async: smol源码分析-Executor篇

本文详细分析了smol异步运行时中的Executor部分,包括Thread Local Executor、Blocking Executor和Work Stealing Executor。每个Executor的结构、工作原理及关键函数如spawn、execute等进行了阐述。smol::run作为驱动,执行future并管理各种Executor。此外,还讨论了smol的使用注意事项,如手动启用运行时和避免线程嵌套。
摘要由CSDN通过智能技术生成

本文来自知乎:https://zhuanlan.zhihu.com/p/137353103

作者:赖智超

smol是一个精简高效的异步运行时,包含有Executor,Reactor和Timer的实现。本文分析其中的Executor部分,借助于async_task(之前的文章已经详细分析过了)打下的基础,executor的实现非常清晰简洁,整个代码几个小时就能分析完毕。smol实现的executor有三类:

  1. thread-local:用于执行!Send的task,由Task::local创建;

  2. work-stealing: 多线程带工作窃取,执行Task::spawn创建的Task;

  3. blocking executor: 线程池执行带阻塞的task,由Task::blocking创建;

smol::run会执行参数提供的future至结束,同时充当工作线程的角色执行thread-local,work-stealing中spawn出来的task以及推进reactor中的IO事件和定时器。另外也提供了smol::block_on方法,来执行单个future。下面分别分析各个executor的实现细节。

smol整体结构图

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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值