有栈协程和无栈协程

有栈协程和无栈协程

协程

协程coroutine是用户级线程,由应用程序而非系统内核控制线程调度和上下文切换,是非抢占的、主动让出CPU资源的多线程。和内核级线程thread相比,用户级线程优点在于内存占用小、切换成本低、能大量创建,如果配合非阻塞式API能够处理大规模并发,缺点是没法发挥多核的性能、同一时刻一个CPU上只有一个协程,但是可以通过在内核级线程中使用协程间接发挥多核性能。例如Golang利用内核支持,可以在多核CPU上执行协程。

有栈和无栈协程

协程的实现分为有栈协程(stackful)和无栈协程(stackless)两种。有栈协程指每个协程会保存单独的上下文(执行栈、寄存器等),协程的唤醒和挂起就是拷贝、切换上下文;无栈协程指单个线程内所有协程都共享一个执行栈,协程的切换就是简单的函数返回。

函数调用栈

调用栈是一段连续的地址空间,无论是caller(调用方)还是callee(被调用方)都位于这段空间之内。而调用栈中一个函数所占用的地址空间我们称之为「栈帧」(stack frame),调用栈就是若干个栈帧拼接而成的。下图就是一个典型的x86系统的栈结构,里面存储了main()和pow()两个函数的栈帧。

x86函数调用栈图

从图中可以看出,调用栈是一个用来跟踪程序运行状态的数据结构,记录了程序每一次函数调用的位置、参数和返回值等信息(由编译器维护)。这包括了函数的整个执行过程。函数的上下文包括这个函数的栈帧和此时寄存器的值。

有栈协程

函数运行在调用栈上,把函数作为一个协程,那么协程的上下文就是这个函数及其嵌套函数的(连续的)栈帧存储的值,以及此时寄存器存储的值。如果我们调度协程,也就是保存当前正在运行的协程上下文,然后恢复下个将要运行的协程的上下文。这样我们就轻松的完成了协程调度。并且因为保存的上下文和普通函数执行的上下文是一样的,所以有栈协程可以在任意嵌套函数中挂起(无栈协程不行)。

有栈协程的优点在易用性上,通常只需要调用对应的方法,就可以切换上下文挂起协程。在有栈协程调度时,需要频繁的切换上下文,开销较大。单从实现上看,有栈协程更接近于内核级线程,都需要为每个线程保存单独的上下文(寄存器、栈等),区别在于有栈协程的调度由应用程序自行实现,对内核是透明的,而内核级线程的调度由系统内核完成,是抢占式的。

例如Golang在进行协程调度时会保存栈寄存器和程序计数器(汇编支持),再进行协程调度。

无栈协程

相比于有栈协程直接切换栈帧的思路,无栈协程在不改变函数调用栈的情况下,采用类似生成器(generator)的思路实现了上下文切换。通过编译器将生成器改写为对应的迭代器类型(内部实现是一个状态机)。

而无栈协程需要在编译器将代码编译为对应的状态机代码,挂起的位置在编译器确定。无栈协程的优点在性能上,不需要保存单独的上下文,内存占用低,切换成本低,性能高。缺点是需要编译器提供语义支持,无栈协程的实现是通过编译器对语法糖做支持,比如C#的yield return, aysnc\await,编译器将带有这些关键字的方法编译为生成器,以及对应的类型作为状态机。

只有状态机的支持才能进行协程调度,例如Rust中的tokio,基于Future的用户态线程,根据poll方法获取Future状态,它不可以在任意嵌套函数中挂起(同步代码未实现状态机)。

总结

有栈协程无栈协程备注
例子Golang goroutineRust async\await
是否拥有单独的上下文上下文包括寄存器、栈帧
局部变量保存位置无栈协程的局部变量保存在堆上,比如generator的数据成员
优点1. 每个协程有单独的上下文,可以在任意的嵌套函数中任何地方挂起此协程
2. 不需要编译器做语法支持,通过汇编指令即可实现
1. 不需要为每个协程保存单独的上下文,内存占用低
2. 切换成本低,性能更高
缺点1. 需要提前分配一定大小的堆内存保存每个协程上下文,所以会出现内存浪费或者栈溢出
2. 上下文拷贝和切换成本高,性能低于无栈协程
1. 需要编译器提供语义支持,比如async\await语法糖
2. 只能在这个生成器内挂起此协程,无法在嵌套函数中挂起此协程
3. 关键字有一定传染性,异步代码必须都有对应的关键字。作为对比,有栈协程只需要做对应的函数调用
无栈协程无法在嵌套函数中挂起此协程,有栈协程由于是通过保存和切换上下文包括寄存器和执行栈实现,可以在协程函数的嵌套函数内部yield这个协程并唤醒

参考

[1]有栈协程与无栈协程

  • 29
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值