rust异步运行时设计
在了解无栈协程前,我们先看看有栈的情况,其实这就是普通的函数调用。
有栈协程
在c语言中,函数调用和返回过程中会使用栈上数据进行恢复调用前状态。程序总体上是同步顺序运行的,他们都使用同一个栈。但是随着不断地函数调用,这个栈会越来越深,所以会带来极大的开销。
而有栈协程中的
有栈
到底是什么意思,这里其实是约定成俗想表达对于每个新创建的协程来说:他们都独立运行与一块新的栈,这块栈是从堆(基于mmap维护了整个内存管理)上面申请的
,没用共用系统栈,那么这个协程的生命周期和上下文都能够被完整保存,可以被任意时间和任意线程独立执行
go 创建协程
在golang中可以使用go
关键字创建协程,在创建后整个函数与当前的主线程没有瓜葛,相当于单独开辟一个栈给这个程序使用。
go func(){ |
这里就不深入了解了,主要是需要学习无栈协程。
无栈协程
协程的实现需要保存上下文,但是我又不能依赖栈,因为这样可能会带来其他开销,那我们该如何做到呢?带着这个疑问我们看看rust魔法。
首先看看goland和rust关于协程的语法区别:
func main(){ |
tokio::spawn(async { |
- 由于rust没有自带的运行时,所有协程的调度、执行、切换都需要依赖三方实现,比较好的就是
tokio
- 编译器只干了一件事情:
在有await语句的地方检测是否ready,否则挂起函数,等待下次运行
既然rust不像golang那样有单独的栈,那他怎么实现上下文保存和栈的重入呢?
在了解rust通过await实现协程(特别强调await)前来一起看看什么叫做状态机吧。
状态机
理解协程:在特定的位置暂停(注意这不是阻塞),接着去执行其他协程,然后被唤醒后重新在当前恢复。
golang使用过独立的栈做到,而rust协程是通过状态机实现。这一点区别很重要。
重点
: rust编译器会将带有.await的代码快转换为一个enum 状态机。- 对于每个await的代码都实现为一个enum的分支
- 每次协程的暂停和恢复只是进入不同的代码分支罢了
虽然没有了额外的占的开销,但实际上编译器会生成很多指令和分支来支持这个状态机
不过相比需要额外的栈内存来实现协程,这种方式已经非常棒了
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ya0rk の Blog!