关于协程的一些收获 - (sunznx) 振翅飞翔
27 March 2020

总结最近几天在看关于协程实现的一些收获:

  • 什么是协程
    简单来讲就是 “一个回调函数加参数”
  • 协程的神奇之处
    我们在调用 “协程(回调函数)” 的时候,CPU 的 “寄存器” 会发生变化,所以需要做一些 “处理” 来保存这些寄存器的变化,这样我们就能实现类似 OS 调度 “进程/线程”。
  • 协程的 “栈”
    上面提到,在协程里面,需要保存寄存器的状态。因此,创建的协程的时候都会指定一个 “栈顶” 和 “栈大小”,就是用来干这事。

    协程的 “栈” 有两种实现:

    1. 每次创建的时候就分配固定的大小
    2. 创建的时候就不分配内存, 等协程运行完需要保存状态了 ,再计算这协程用了多少内存,最后将 “本次使用的内存块” 存到协程本身里面。
      这里有两个问题要解决:协程的状态保存在哪里?怎么计算协程用了多少内存。
      协程的状态保存在一个 “公共栈” 中。
      计算协程用了多少内存实际上不好直接计算,我们只知道栈顶,但是并不知道这个协程用的栈容量是多少。在 “云风” 的 协程实现 里面,是通过创建一个变量,然后根据这个变量的地址来算出来使用了多少内存(因为此时新建的变量也是在 “公共栈” 上的)

      协程恢复:(在协程暂停的时候,我们已经把协程的栈保存到了协程本身了)直接 copy 协程的栈到 “公共栈” 里面

    ps:
    还有一种是无栈的协程(见很少有人提到,不了解)
    在实现上,公共栈可以分多个,这样可以省点 copy 内存的时间 最近都流行实现 Coroutine 么? - 知乎

  • 系统调用 hook
    linux c 的系统函数是可以 hook 的(如果这些函数使用的是动态链接库的话,例如 libc.so,通过 dlsym )。腾讯的 libco 协程框架整是使用这种来实现 hook
    像 swoole 这种,不需要通过 hook 系统调用,直接 hook php 源码对应调用 socket、sleep 等阻塞函数

    hook socket 的方式:例如,注册一个 pollfd,然后再将 pollfd 的 fd 注册到最外层调度器的 epoll 上

  • 对称协程和非对称协程
    参考 微信开源C++Libco介绍与应用(一) - 知乎
    正在运行的协程执行完了,或者是手动 yield,那么就切换到上一个协程了。(云风实现的协程就是这种)
    对称协程:启动之后就跟启动之前的协程没有任何关系了。协程的切换操作,一般而言只有一个操作 — yield,用于将程序控制流转移给另外的协程。对称协程机制一般需要一个调度器的支持,按一定调度算法去选择 yield 的目标协程
  • 协程的缺点
    最怕的就是 bug 和不兼容(例如 swoole)
    还有就是无法利用多核(可以开多个调度器,每个调度器在不同的进程上)
  • 感兴趣以及以后的探索
    就是看看 go 是如何实现协程的(虽然感觉大同小异),主要是看看 go 的栈是如何处理的,以及如何利用多线程