周末偶然看到这篇 Coroutines in C,作者利用达夫设备(Duff’s Device)定义了几个宏,在 C 语言中模拟了协程的实现。然后又警告读者不要在公司写这样的代码 😅:

Of course, this trick violates every coding standard in the book. Try doing this in your company’s code and you will probably be subject to a stern telling off if not disciplinary action! . . . It’s a wonder you haven’t been fired on the spot for such irresponsible coding practice. You should be ashamed of yourself.

甚至连达夫本人都吐槽道 🥲:

The “revolting way to use switches to implement interrupt driven state machines”

原理就是利用 C 语言的两个特性实现:

  • switchcase 支持和 for , while 循环语句交叉 : 所以 switch语句支持直接跳转到循环内部
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int f() {
  static int i, state = 0;
  switch(state) {
    case 0:
    for(i = 0; i < 10; i++ ) {
      state = 1;
      return i;
      case 1:;
    }
  }
}
  • 全局变量保存栈变量和需要跳转的位置: static int i, state = 0;

一个完整的例子如下:

 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
32
33
34
35
36
37
38
#include <stdio.h>

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

// 模拟生产者,读取并返回一个字符
int produce() {
  static int i;
  crBegin;
  while(1) {
    i = getchar();
    getchar();
    if (i == EOF) break;
    printf("produce %c\n", i);
    crSuspend(i);
  }
  crFinish(EOF);
}


void consume(int i) {
// 模拟消费者,打印字符
  crBegin;
  while(i != EOF) {
    printf("consume: %c\n", i);
    crSuspend();
  }
  crFinish();
}

int main() {
  int t = 10;
  while(t--) {
    consume(produce());
  }
}

看完觉得不过瘾,又去搜了下 Stackful / Stackless Coroutine 的区别,在 Hack News 的讨论里点进去看了几篇关于 Coroutine 的论文 🙄:

  • Revisiting Coroutines:对 coroutine 的分类和概念的定义
    • Symmetric/Asymmetric coroutine: Asymmetric coroutine 提供两个操作:一个调用 coroutine,另一个挂起 coroutine。挂起操作将程序控制流返回给调用者;Symmetric coroutine 只提供一个操作,就是将控制流切换到其他 coroutine,并只在同一层级进行调度;
    • Stackful/Stackless coroutine: Stackful 表示每个 coroutine 都有独立的栈,每次挂起和恢复都需要保存寄存器变量到栈中。
  • Coroutines in Lua:Lua 实现的 coroutine 是 Stackful 和 Asymmetric coroutine

所以上面利用达夫设备实现的 coroutine 是 Stackless 和 Asymmetric 的 coroutine。

然后想起自己大学的时候在 AVR MPU 上写过一个 Coroutine 的实现。嗯。。。就去 Review 了一下自己 3 年前的代码,提了几个 Issue 就跑 🤔。主要是对几个概念进行澄清:

  1. cooperative multitasking 和 preemptive multitasking:

也就是说,ROS 不是 cooperative multitasking,而(算)是  preemptive multitasking

  • 低优先级的任务会被高优先级的无条件抢占
  • 在任务的 priority 相等时,ROS 采用 round-robin 根据时间片调度
  • 低优先级任务可能一直得不到调度,除非高优先级任务主动 suspend
    • 换个角度,对于高优先级任务来说是 cooperative multitasking:除非主动 suspend ,否则不会被抢占。
    • 对于低优先级任务来说是 preemptive multitasking:会被高优先级的无条件抢占
  1. Symmetric coroutine 和 Asymmetric coroutine:

需要声明的是, ROS 实现的协程是  symmetric coroutine。因此任务不能嵌套调用(不存在调用者和被调用者的关系),任务都是在同一层级进行调度的。

  1. ROS 实现的是 stackful coroutine,为每个任务申请单独的栈。

  2. 目前 ROS 提供的系统调用有如下四个:

    • ros_init(): 初始化系统时钟等
    • ros_create_task(*task, task_func, priority, stack, stack_size): 创建任务
    • ros_delay(ticks): 挂起任务一段时间,主动让出 CPU
    • ros_schedule(): 任务调度,不必主动调用

    系统调用的功能十分有限和简洁简陋,后续会考虑提供更多系统调用,丰富任务间协同通信的能力:

    • ros_yield(x): 挂起任务并返回值,唤醒任意其他的任务
    • ros_join(*task): 等待其他任务结束
    • Channel: 任务间数据传递,类似 Go 中的  chan
    • Mutex: 锁

完。