基于Duff's Device的C简易无栈协程实现

参考 Simon Tatham 的文章https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html

译文为https://mthli.xyz/coroutines-in-c/

协程是一组序列化的子过程,与线程不同,协程的调度是由用户而非操作系统执行的,协程可以在任意时刻让出CPU(称为yield操作),下次调用时从上次yield的地方继续执行

Simon Tatham利用Duff's Device实现了一种简易的无栈协程,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
int function(void) {
static int i, state = 0;
switch (state) {
case 0:
for (i = 0; i < 10; i++) {
state = 1;
return i;
case 1:;
}
}
}

该函数的作用是第i次调用时返回i,最多10次(相同的函数功能可以通过一个statci变量实现,但本文是为了讨论协程),其核心部分为switch语句以及return前后两句,通过设置不同的state来保证下一次调用时从上次退出的地方继续执行(如果不明白switch为何能和for套在一起写可以自行搜索Duff's Device)

可以看出,每次调用return时设置的state必须不同,可以利用__LINE__宏来设置state,这样只要不在一行调用两次return即可

以下是将其用宏封装的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>

#define crBegin static int state = 0; switch (state) { case 0:
#define crReturn(x) do { state = __LINE__; return x; case __LINE__:; } while (0)
#define crFinish }

void f1() {
crBegin;
puts("1");
puts("2");
crReturn();
puts("3");
crFinish;
}

void f2() {
crBegin;
puts("x");
crReturn();
puts("y");
puts("z");
crFinish;
}

int main (void) {
f1();
f2();
f1();
f2();
return 0;
}

do while(0)是为了让crReturn与if else嵌套时不用考虑大括号问题

这份代码运行后会依次输出1,2,x,3,y,z

至此,一份用C实现的简易无栈协程就完成了,需要注意的是,因为switch内部不能任意定义变量,请在crBegin之前定义所需变量

PS:这份代码仅供学习参考,如果你在实际生产代码中用了可能会被上司打死