参考资料
一、什么是协程
1.1 基础知识
通用的说法是协程是⼀种“轻量级线程”,用户态线程”。
- 可以减少用户态与内核态的切换,相当于一个花里胡哨的函数。
协程的本质就是函数和函数运行状态的组合 。
- 函数一旦被调用,则只能从头运行到结束
- 协程可以执行到一半退出(yield),暂时让出CPU执行权;在后面适当时机可以重新恢复运行(resume)。这期间CPU可以运行其他协程。
协程能够半路yield、再重新resume的关键是协程存储了函数在yield时间点的执⾏状态,这个状态称为协程上下文。
- 通过记录当前执行状态下CPU全部寄存器的值来实现
- 要resume时再把这些值重新设置给CPU
与线程的区别:
- 线程创建之后,线程的运⾏和调度也是由操作系统⾃动完成的
- 协程创建后,协程的运⾏和调度都要由应用程序来完成,就和调用函数⼀样,所以协程也被称为“用户态线程”。
1.2 优缺点
优点:
- 提高资源利用率,提高程序并发性能。协程允许开发者编写异步代码,实现非阻塞的并发操作,通过在适当的时候挂起和恢复协程,可以有效地管理多个任务的执行,提高程序的并发性能。与线程相比,协程是轻量级的,它们的创建和上下文切换开销较小,可以同时执行大量的协程,而不会导致系统负载过重,可以在单线程下实现异步,使程序不存在阻塞阶段,充分利用cpu资源。
- 简化异步编程逻辑。使用协程可以简化并发编程的复杂性,通过使用适当的协程库或语言特性,可以避免显式的线程同步、锁和互斥量等并发编程的常见问题,用同步的思想就可以编写成异步的程序。
缺点:
- 无法利用多核资源。线程才是系统调度的基本单位,单线程下的多协程本质上还是串行执行的,只能用到单核计算资源,所以协程往往要与多线程、多进程一起使用。
二、协程的分类
2.1 对称协程与非对称协程
- 对称协程,协程可以不受限制地将控制权交给任何其他协程。任何⼀个协程都是相互独⽴且平等的,调度权可以在任意协程之间转移。
- ⾮对称协程,是指协程之间存在类似堆栈的调⽤⽅-被调⽤⽅关系。协程出让调度权的⽬标只能是它的调⽤者。
对称协程更灵活,⾮对称协程实现更简单。
2.2 有栈协程与无栈协程
浅谈有栈协程与无栈协程
有栈协程:用独立的执行栈来保存协程的上下文信息。
- 当协程被挂起时,栈协程会保存当前执行状态(例如函数调用栈、局部变量等),并将控制权交还给调度器。
- 当协程被恢复时,栈协程会将之前保存的执行状态恢复,从上次挂起的地方继续执行。
无栈协程:它不需要独立的执行栈来保存协程的上下文信息,协程的上下文都放到公共内存中,
- 当协程被挂起时,无栈协程会将协程的状态保存在堆上的数据结构中,并将控制权交还给调度器
- 当协程被恢复时,无栈协程会将之前保存的状态从堆中取出,并从上次挂起的地方继续执行。
- 协程切换时,使用状态机来切换,就不用切换对应的上下文了,因为都在堆里。
2.3 独立栈与共享栈
独立栈和共享栈都是有栈协程。
共享栈本质就是所有的协程在运行的时候都使用同一个栈空间,每次协程切换时要把自身用的共享栈空间拷贝。
- 好处是使用公共资源,公共资源内存空间比较大,相对安全,节省内存空间,
- 坏处是协程频切换需要进行内存拷贝,废CPU
独立栈,也就是每个协程的栈空间都是独立的,固定大小。
- 好处是协程切换的时候,内存不用拷贝来拷贝去,相对简单
- 坏处则是内存空间浪费,容易栈溢出。
三、C++的协程
参考资料2。
在 C++ 当中,区别一个函数是不是协程,是通过它的返回值类型来判断的。一个函数的返回值类型如果是符合协程的规则的类型,那么这个函数就是一个协程。
C++ 协程会在开始执行时的第一步就使用 operator new 来开辟一块内存来存放这些信息,这块内存或者说这个对象又被称为协程的状态(coroutine state)。
关键字co_await ,使当前函数(协程)的执行被挂起。其操作对象为等待体(awaiter)。
等待体一般需要实现三个函数,用于挂起和恢复时使用。
- await_ready:协程挂起
- await_suspend:用于保存局部变量和挂起点,最为核心的函数之一了
- await_resume:挂起恢复