我最近打算学习下rust的async runtime。我先是看了些对一些概念,比如Future,之类的介绍,然后就是一些runtime的示例代码。然后我打算自己写一个简单的runtime,加深下印象。
我平时用C语言,也用epoll封装过事件驱动的lib,所以打算先用C语言的epoll写个简单的echo服务。然后移植到rust的mio库上。再把程序中的 运行时 和 业务逻辑 区分开,尽量抽象出Future的概念,类似runtime的原型。然后再慢慢向标准的runtime靠拢。
为了简单,我开始是定义了自己的Future定义:
enum MiniPoll {
Pending,
Ready,
}
trait MiniFuture {
fn poll(&mut self) -> MiniPoll;
}
跟标准库里的Future对比,有3个区别:1.没有用Pin;2.没有Context(暂时全部用全局static变量);3.没有返回值Output。
前2个区别只是为了简化实现,影响不大。问题出在第3个区别上,也就是Output。最开始简单的echo服务程序里,两个Future(一个是accept,一个是连接上执行echo功能)都没有返回值,所以无所谓。简单的runtime原型也跑起来了。现在是想给Future加上返回值。问题就出在这里。
现在没有Output,所以程序在维护一个task列表的时候,是这么定义的:
Slab<Box<dyn MiniFuture>>
Vec<Box<dyn MiniFuture>>
如果要加上trait关联类型Output,变成 MiniFuture<Output=T>,那么上述的两个列表就不能这么定义了。就是不能把不同类型返回值的Future放到一个Slab或者Vec中!!!但是runtime总是要管理全部的Future的。到这里,我就不知道该怎么办了。
====
我看了一篇对 async-task 的源码介绍的文章。里面有句话:“提供了 JoinHandle,这样spawn函数对Future没有 Output=()的限制,极大方便用户使用”。是不是说 Output类型,本来就是个难缠的问题?
另外,看了这篇文章里贴的Task的定义的源码,发现都是unsafe代码,手动定义vtable,手动用Output覆盖future内存。感觉又回到了C语言的风格,直接操作内存。
====
我的问题:
-
如果要管理带不同关联类型Output的Future,要怎么做?是不是一定要unsafe才行?
-
rust的async runtime跟C里的libev这种库,难度不是一个层次的?要难得多。另外,对于普通程序员即便不了解runtime,也并不影响对async/await的使用?
====
这个介绍runtime的文章里,也有一个非常小的例子。这个例子里task的定义里,Future的Output也是写死了是 ()
,而不是泛型T
:
struct Task {
future: Pin<Box<dyn Future<Output = ()> + 'static>>,
waker: Waker,
}
pub struct BasicExecutor {
tasks: Vec<Task>,
}
这个就是我上面遇到的问题:如何定义全部(不同Output返回类型的Future)的tasks列表: Box<dyn Future<Output=T>>
?
评论区
写评论好的。我改下思路。先去学习下smol的代码。据说很短,比较容易学习。
首先,重新定义Future是没有任何意义的,劝你尽早停止浪费你的时间; 其次,我也说过了,task和future是两种东西,Future有output但是task没有,一开始混为一谈就是错,你根本在和不存在的问题搏斗;而且这从来也是一个很细微的问题,并不存在什么原则性的困难😓,教程为了只是为了简化问题才限制Output=(),而async-std是一个生产级的开源库,它不需要回避这种问题; 贴上我之前实现的玩具executor的Task定义,省略实现仅供参考;
有没有推荐的文章?
直接看代码太痛苦了。本来想的是能自己先写一个简单的runtime(不需要多线程,也最好不用unsafe),有助于后续阅读现有的库。现在发现连最基本的流程都跑不通。
--
👇
Ryan-Git: 建议先了解现有库的实现逻辑,最好和历史。 基本上,要 work stealing 还要性能比较好的话,unsafe + vtable 是目前唯一的选择。
重新定义Future的目的是:我想从最简单的定义开始,按需去加特性。后续按需加上Pin,Context等,慢慢向标准库靠拢。这样可以更好的理解每个特征的真正需求。
当然这是我自认为的。不一定真的对理解有帮助。
--
👇
aj3n: 1. 如果能作出适当取舍,unsafe不是必须的;Task和Future不是对等的,你不应该这么定义Task集合;我也不太明白你重新定义Future的意义,你如果只是想重新实现runtime,只需要设计你的Task结构就好了; 2. 不好评价难度,只针对后面的提问,写rust异步代码完全可以先上车后补票,不了解原理并不会影响使用(大部分情况?),但是肯定会有一些细微的坑🤦
建议先了解现有库的实现逻辑,最好和历史。 基本上,要 work stealing 还要性能比较好的话,unsafe + vtable 是目前唯一的选择。