我之前听说协程是在看到python的yield时,但是当时也没深究是啥。但是最近,学妹让我帮她看一下操作系统实验里面,设计协程库该怎么设计,我就稍微看了一下。这里稍微整理记录一下。

赞一下jyy的OS小实验的设计,非常有营养。

什么是协程(coroutine)

定义

按照英文维基上的说法:

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes.

我感觉这里描述得还是非常精髓的,有几点: 1. 协程算是子常式调用的一种推广,让它可以应用与cooperative multi-tasking 2. 它允许子常式的执行suspend然后resume。

关键的是下面的几节,分别和subroutine以及thread的对比。

协程例子

var q := new queue

coroutine produce
loop
while q is not full
create some new items
add the items to q
yield to consume

coroutine consume
loop
while q is not empty
remove some items from q
use the items
yield to produce

从这个例子看,协程和线程非常像,yield时,就切换到其他人执行。结束了就结束了。但是如果从用协程实现的生成器的例子来看,又和函数调用非常像。

与subroutine对比

Subroutines are special cases of coroutines.

Subroutine与coroutine的区别主要是在于执行流上。subroutine是执行一遍,结束后就返回父函数。它的多次执行间是不保存现场的。subroutine和父函数的控制流算是紧耦合的。 而coroutine和创建它的函数之间是松耦合的,就像线程一样,所以似乎也没啥函数调用链这种关系。执行的时候,如果结束了,就是执行流结束了。如果没结束,它可以yield主动切换到其他人去执行。所以感觉coroutine更像线程。

与thread的对比

coroutine和thread的主要区别就是coroutine是cooperatively multitasked,不是抢占式多任务。它提供了concurrency,而不是parallelism。

协程的实现

介面:

void co_init();
struct co *co_start(const char *name, func_t func, void *arg);
void co_yield();
void co_wait(struct co *thd);

介面说明:

用协程的程序会首先调用co_init完成一些必要的初始化。如果你的实现并不需要在启动时做任何初始化,你可以留下一个空的函数。
co_start 创建一个新的协程,并返回一个指针(动态分配内存)。我们的框架代码中并没有限定 struct co 结构体的设计,所以你可以自由发挥??。
co_yield 是指当前运行的协程放弃执行,并切换到其他协程执行。系统中可能有多个运行的协程(包括当前协程)。你可以随机选择下一个系统中可运行的协程。
co_wait(thd) 表示当前协程不再执行,直到 thd 协程的执行完成。我们规定,每个协程的资源在co_wait()等待结束后释放,因此每个协程只能被co_wait一次。更精确地说,每个协程必须恰好被co_wait一次,否则就会造成资源泄露。

介面实现:

要搞多线程,多进程,协程,用户态的,内核态的,本质上都是保存多个执行流的状态,现场(寄存器,堆栈,以及各种相关的资源),然后通过调度,切来切去,所以实现起来都是差不多的。

function co_init
# 初始化数据结构
# 记得给主线程分配一个数据结构
# 即进来的main函数所在的主线程
# 本质上也是一个可供调度执行的用户态线程

function co_start(func)
# 创建新线程,新线程将执行func函数
# 但是func函数将由wrapper函数包装著
t = alloc_new_thread(wrapper, func)
# schedule,就可以让新创建的函数跑起来了
schedule()
return t;

function wrapper(func)
# 真的执行func
call function
# func执行完了,标记为结束,不可执行
# 等待被wait回收
# mark as dead
# schedule到其他控制流,possibly主线程
schedule

function co_yield
schedule

function co_wait(th)
# 等待th结束
while th not dead
schedule
# 回收th所占用的资源
deallocate th

function schedule
# 保存现场(寄存器,栈)
# 挑选可执行的coroutine
# 切换过去(寄存器,pc,栈)
# 回来后,将到达这里
# schedule函数用setjmp和longjmp来实现

推荐阅读:

相关文章