当前位置:   article > 正文

C++协程_c++ 协程

c++ 协程

产生背景

对于后台开发我们一个重要问题即使用尽可能少的服务器资源处理海量的请求,除了我们再架构上做多机自动扩容外,我们还必须尽可能提高单机硬件的利用率(CPU利用率+IO利用率)

为了提高硬件的利用率往往我们采用三种技术路径:

  • 多线程
  • 异步IO
  • 协程

协程和异步IO以及多线程的对比

多线程

多线程可以充分利用CPU的多核,实现真正的并行,它是操作系统的基础设施。但是线程是一个粗粒度、相对比较笨重的多任务的抽象机制,例如以下几个特点

  • 创建线程非常耗时
  • 线程上下文切换非常耗时(要从用户态切换到内核态;保存当前线程执行环境;加载目标线程的执行环境;再从内核态切换回来等一系列操作)
  • 每个线程会预先分配一个几M的调用栈
  • 系统中最多只能同时运行数千个线程(当就绪线程个数远大于CPU核数,操作系统大量的时间就会用来进行线程切换,性能急剧下降)

异步IO

操作系统中有两种IO:同步IO和异步IO(5种I/O模型)

同步IO:发起IO(比如read)后,CPU必须原地等待其结束,然后才能继续往下执行

异步IO:发起IO(比如read)前,会先注册一个回调函数;发起IO后,CPU无需原地等待其完成,而是继续往下执行;CPU收到IO完成的中断信号后,将调用回调函数处理IO完成后的剩下的工作

即:同步IO的逻辑直观,但是性能较低,仅适用少量IO;异步IO性能好,但是逻辑复杂,适用于大规模并发IO

ps:CPU执行速度是远大于IO响应速度的,若执行同步IO,到达某一个函数(read执行IO操作),这个函数执行5分种,那么当前线程就必须原地等待5分钟(忙等待),才能执行后续代码,这对于CPU资源十分浪费。但是在异步IO模式下,CPU和IO可以全速并行执行,使用率大大提高

协程

多线程和异步IO的缺点即是协程的优点

  • 协程的特点在于是一个线程执行,最大的优势就是协程极高的执行效率。因为创建或切换协程不是线程创建/切换,它相当于一次函数调用,由程序自身控制,和多线程比,线程数量越多,协程的性能优势就越明显。
  • 协程不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
  • 每个协程通常只需要几十个字节保存相关状态信息,空间开销远低于线程栈;
  • 系统种可同时运行数千万个协程,数量主要取决于可用内存大小(当然cpu核数也限制)
  • 协程在性能与异步IO同样,但是在逻辑上和同步IO一样直观

ps:协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。

处理IO密集型任务

对于大多数后台程序面临的都是IO密集型任务,它的处理流程大致如下:

  1. 侦听线程收到一个客户端请求
  2. 侦听线程吧请求加入一个队列,然后继续侦听下一个请求
  3. 工作线程从队列种取出一个请求,然后发起IO
  4. IO完成后,向客户端返回请求结果

用以上三种方式怎么实现这种架构

架构描述
多线程架构侦听线程收到一个客户端请求之后创建一个工作线程(或从线程池种申请一个空闲的线程)处理该请求,工作线程发起同步IO后,原地等待其完成;然后向客户端返回请求
异步IO架构工作线程从队列中取出一个请求之后,注册一个回调函数并发起一个异步IO;然后立刻返回,周而复始的处理下一个请求;IO完成后,回调函数负责向客户端返回结果
协程架构工作线程从队列中取出一个请求之后,调用一个处理协程;然后立即返回,周而复始的处理下一个请求;处理协程负责发起IO;IO完成后,发起IO的处理协程向客户端返回请求结果

三种架构的对比

架构特点
多线程逻辑直观、容易实现,但不能充分利用后台服务器硬件资源,容易发生雪崩
异步IO逻辑杂乱、维护麻烦,但性能卓越,目前依旧是后台服务器的主流架构
协程性能卓越且逻辑直观,是后台服务器架构的演进方向

协程的实现机制

协程和线程的实现很相似,每个线程都有一个对应的线程函数,每个协程也有一个对应的协程函数;线程函数和普通函数没有区别,但是协程函数和普通函数有区别。

普通函数:每次调用只能从第一条语句开始执行

协程函数:协程函数交出控制权后,可以再次从交出控制权的下一语句开始执行(类比多线程的调度方式)

ps:普通函数每次执行都会从入口进入,当A函数调用B函数,那么只有当B函数执行完毕才能再去执行A剩余的代码,函数做不到当B执行一半,再去调用A可以从A剩余部分往下执行(每次调用函数都会从函数入口重新开始)。但是协程就可以做到,这种方式和线程十分相似。因为调用普通函数时,调用方的返回地址、入口参数等信息都保存在栈上。函数返回后,栈上的信息会自动清除,所以每次调用普通函数都只能从第一条语句开始

 

协程函数特点

  1. 首次调用协程函数,会从堆中分配一个协程上下文,调用方的返回地址、入口函数、交出控制权等信息保存在协程上下文中
  2. 当协程中途交出控制权后,协程上下文不会被删除(相当于函数退出之后,上下文环境还被保存了,类比线程切换)
  3. 当协程再次获得控制权后,会自动从协程上下文中恢复调用环境,然后从上一次交出控制权的下一条语句继续执行(加载目标协程环境,类比线程切换)
  4. 协程函数返回(非中途交出控制权)后,协程上下文将被删除
  5. 若再次调用协程函数,视为首次调用

ps:上面所描述的是协程实现的一般原理,根据实现方式不同,可分为有栈协程和无栈协程

有栈协程

技术路线:一个线程可以创建大量协程,每个协程都会保留一个私有栈,协程一旦执行到异步IO处,就会主动交出控制权。同一线程的所有协程以串行方式协作执行,没有数据争用的问题

有栈协程的特点

ps1:为了减少有栈协程的空间开销,有些协程框架用一个共享栈代替每个协程的私有栈。共享栈虽然降低了协程的空间开销,但却引进了栈拷贝的时间开销。

ps2:对称协程调度逻辑复杂,应用的场景有限,非对称协程是有栈协程的主流

有栈协程可通过操作系统提供的系统调用实现

OS系统调用
Linuxgetcontext,setcontext,makecontext,swapcontext
WindowsCreateFiber,ConvertFiberToThread,SwitchToFiber

无栈协程

技术路线:将异步IO封装到协程函数中,协程函数发起异步IO后,立即保存执行环境,然后吧控制权交给调用方(Caller),调用方继续往下执行;异步IO完成后,负责处理IO完成事件的回调函数获得控制权,回调函数再把控制权转交给发起IO的协程,发起IO的协程首先恢复执行环境,然后从上一次交出控制权的下一条语句继续往下执行

无栈协程特点

ps:通过操作系统原子操作或者各种锁机制可以解决数据争用问题 

 

有栈协程和无栈协程对比

  • 有栈协程的最大缺陷是保存调用栈的开销太大
  • 无栈协程不但具有有栈协程的所有优点,而且空间开销极低;唯一不足就是需要语言标准和编译器支持

 

链接:https://blog.csdn.net/Eunice_fan1207/article/details/91894192

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/254707
推荐阅读
相关标签
  

闽ICP备14008679号