目录
一、开源协程库调研
1、golang语言自带协程
Golang的协程是对称协程,调度器使用了GMP模型。使用go语言写一个并发程序极其简单,例如go func(x,y)即可并发执行一个函数f(x,y),这也是netco的目标:只需要调用接口co_go(func)即可并发执行func函数。
Golang还有一个通道的概念,不同的协程可以往通道中写内容读内容。
当然,重点还是GMP模型,其中G代表的是goroutine协程,Go1.11中协程栈默认是2KB。M代表的是machine,对应的是一个线程。P代表的是processor,当P有任务时需要创建或者唤醒一个系统线程来执行它队列里的任务。所以P/M需要进行绑定,构成一个执行单元。
首先创建一个G对象,G对象保存到P本地队列或者是全局队列。P此时去唤醒一个M。P继续执行它的执行序。M寻找是否有空闲的P,如果有则将该G对象移动到它本身。接下来M执行一个调度循环(调用G对象->执行->清理线程→继续找新的Goroutine执行)。
M执行过程中,随时会发生上下文切换。当发生上下文切换时,需要对执行现场进行保护,以便下次被调度执行时进行现场恢复。Go调度器M的栈保存在G对象上,只需要将M所需要的寄存器(SP、PC等)保存到G对象上就可以实现现场保护。当这些寄存器数据被保护起来,就随时可以做上下文切换了,在中断之前把现场保存起来。如果此时G任务还没有执行完,M可以将任务重新丢到P的任务队列,等待下一次被调度执行。
2、云风的coroutine协程库
风云的协程库是使用C语言实现的使用共享栈的一个非对称协程库,即调用者和被调用者的关系是固定的,协程A调用B,则B完成后必定返回到A。因为云风不希望使用他的协程库的人太考虑栈的大小,并且认为,进行上下文切换的大多时候,栈的使用实际并不大,所以使用共享栈每次进行上下文切换时拷贝的开销其实可以接受。另外,该库的上下文切换使用的是glibc的ucontext。
3、腾讯的libco协程库
腾讯的libco协程库是一个非对称的协程库,结合了epoll机制,其接口风格类似pthread,使用起来实际上已经有了使用线程的感觉。其栈空间(Separate coroutine stacks)的固定大小为128K,也可以使用共享栈(Copying the stack),但默认还是使用固定的栈空间。libco算是给了我极其之大的震撼,因为还是第一次看到结合epoll和hook系统调用的技术,有些叹为观止。另外,该库是自己使用汇编写的上下文切换方法。
4、魅族的libgo协程库
libgo是一个go风格的c++11对称协程库,它的命名结构分为Scheduler,Processer和Task(协程),schedule 负责整个系统的协程调度,协程的运行依赖于执行器 Processer(简称 P),因此在调度器初始化的时候会选择创建 P 的数量(支持动态增长),所有的执行器会添加到双端队列中。该库的命名结构很有意思,所以我在我的netco也采用了类似的命名:Scheduler->Processor->Coroutine。另外,该库使用的是boost库的上下文切换方法。
二、netco协程库概述
基于对上述协程库的调研,我写了netco协程库,它是一个线程风格的纯C++11对称协程库,并且可以用于高并发网络编程。
在使用上,受golang的影响很大,所以我尽可能地减少使用接口,让使用更加轻便简洁。目前和协程相关的接口只有三个:co_go(func),运行一个协程,co_wait(t