对 Coroutine 的不成熟理解

2021/12/29 编译原理

在需要恢复控制权的位置设置一系列 label:一个位于开始位置,另一个在每个 return 语句后面。我们还设置了一个 state 变量,用于在多次函数调用时告诉我们下次应该在哪里恢复控制权。在每次返回前,都需要更新 state 变量,使其指向正确的 label。而在调用后,我们都会通过 switch 对 state 进行判断,以便找到下一次要跳转的 label。

#include <stdio.h>

int function(void) {
    static int i, state = 0;
    switch (state) {
        case 0: goto LABEL0;
        case 1: goto LABEL1;
    }

    LABEL0: /* start of function */
    for (i = 0; i < 10; i ++) {
        state = 1; /* so we will come back to LABEL1 */
        return i;
        LABEL1:; /* resume control straight after the return */
    }
}

int main() {
    printf("%d\n", function());
    printf("task 1\n");
    printf("%d\n", function());
    printf("task 2\n");
    printf("%d\n", function());
    printf("task 3\n");

    return 0;
}

但是,上面的代码是丑陋的。其实最糟糕的部分就是需要手动维护一组 label,并且必须放在函数体之间且需要 switch 语句。我们每添加一个 return 说一句,都必须创建一个新的 label 并将其添加到 switch 中;每移除一个 return 语句,都必须移除其对应的 label。这额外增加了我们两倍的工作量。

#include <stdio.h>

int function(void) {
    static int i, state = 0;

    switch (state) {
        case 0: /* start of function */
            for (i = 0; i < 10; i ++) {
                state = 1;
                return i;
                case 1:; /* resume control straight after the return */
            }
    }
}

int main() {
    printf("%d\n", function());
    printf("task 1\n");
    printf("%d\n", function());
    printf("task 2\n");
    printf("%d\n", function());
    printf("task 3\n");

    return 0;
}
#include <stdio.h>

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

int function(void) {
    static int i;
    crBegin;
    for (i = 0; i < 10; i ++) {
        crReturn(1, i);
    }
    crFinish;
}

int main() {
    printf("%d\n", function());
    printf("task: 1\n");
    printf("%d\n", function());
    printf("task: 2\n");
    printf("%d\n", function());
    printf("task: 3\n");
    
    return 0;
}

setjmp/longjmp

#include <stdio.h>
#include <unistd.h>
#include <setjmp.h>

typedef int BOOL;
#define TRUE 1
#define FALSE 0

typedef struct _Context_ {
    jmp_buf mainBuf;
    jmp_buf coBuf;
} Context;

Context gCtx;

// 恢复
#define resume()\
    if (0 == setjmp(gCtx.mainBuf)) \
    { \
        longjmp(gCtx.coBuf, 1); \
    }

// 挂起
#define yield()\
    if (0 == setjmp(gCtx.coBuf)) \
    { \
        longjmp(gCtx.mainBuf, 1); \
    }

void coroutine_function(void *arg)
{
    while (TRUE)
    {
        printf("\n*** coroutine: working\n");

        for (int i = 0; i < 10; ++i)
        {
            fprintf(stderr, ".");
            usleep(1000 * 200);
        }
        printf("\n*** coroutine: suspend\n");

        // 让出 CPU
        yield();
    }
}

typedef void (*pf)(void *);
BOOL go(pf func, void *arg)
{
    // 保存主程的跳转点
    if (0 == setjmp(gCtx.mainBuf))
    {
        func(arg);
        return TRUE;
    }
    return FALSE;
}

int main()
{
    go(coroutine_function, NULL);

    while (TRUE)
    {
        printf("\n=== main: working\n");
        // 模拟耗时操作
        for (int i = 0; i < 10; ++i)
        {
            fprintf(stderr, ".");
            usleep(1000 * 200);
        }
        printf("\n=== main: suspend\n");
        resume();
    }

    return 0;
}

参考

Coroutines in C

Search

    Table of Contents