协程设计原理与实现

时间:2024-12-10 07:10:26

协程设计原理与汇编实现

同步与异步

对于任何一个事情,都可以划分为不同的步骤。所谓同步,就先做第一个事情,按照这件事的步骤完成这件事之后,再去做第二件事。再去做第三件事,以此类推。

异步就是,可以先开始做第一件事,从第一个步骤开始,但是当做到某个步骤之后,需要暂停等待,于是跳转到了第二件事,又开始从第二件事的第一个步骤开始做,当做到某个步骤后,又需要暂时等待,于是跳转到了第三个事件,从第一个步骤开始做,可能做到某个步骤后,发现第一个事情暂停的步骤被唤醒了,于是转过头去继续把后续的事情做完,再去看第二件事等待的资源是否完成。以此类推。

所以从宏观上(即从某个时间段)来看,同步像是分开进行的,异步像是同时执行的。这一点和操作系统中的某些概念是类似的。

什么是协程

协程的核心就是,以同步的方式,实现异步的性能。以上面的例子讲解,就是用不同的函数实现不同的步骤,这样在编程看起来是同步的(就是所谓的以同步的方式),然后不同函数在实现步骤的时候,会设定一个类似于原语的操作,如果这个步骤无法立刻完成,就跳转到下一个事情中去,直到满足条件之后,再回来继续完成该事件(这个原语即是实现了异步的性能)。

如何实现协程

第一种方式:setjmp/longjmp

首先需要定义一个 jmp_buf类型的变量env,代表此时的环境编号,类似于存档点。

setjmp函数:调用此函数,会保存当前系统的堆栈里的数据,进行存档。返回值为0,代表首次进行存档。返回值为x,代表的下一次回退到存档点应该走的路线。

longjmp函数:调用此函数,会直接回退到存档点,其中函数的第二个参数x就是上面setjmp下一次要返回的值。(即下一次要走的路线)

举例:

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

jmp_buf env;

int fun(int arg){
    printf("fun %d \n", arg);
    arg++;
    longjmp(env, arg);
    return 0;
}

int main(){

    int ret = setjmp(env);
    if(ret == 0){
        fun(ret);
    }
    else if(ret == 1){
        fun(ret);
    }
    else if(ret == 2){
        fun(ret);
    }else if(ret == 3){
        fun(ret);
    }

}

弊端

如果是多线程,会出现不同的堆栈,这样在保存的时候,会出现函数未定义等情况。不建议使用

第二种方式:ucontext

ucontext相比于上一个,类似于让用户自己实现了上下文信息的保存,而不是像setjmp一样通过调用函数让系统来保存。

定义了一个ucontext的结构体来保存上下文信息。

//首先调用getcontext(&uc)函数把上下文信息保存在uc结构体中

#include<ucontext.h>
#include<stdio.h>

ucontext_t ctx[3], main_ctx;
int count = 0;

void fun1(){
    while (count ++ < 30){
        printf("1\n");
        swapcontext(&ctx[0], &ctx[1]);
        printf("4\n");
    }
}

void fun2(){
    while (count ++ < 30){
        printf("2\n");
        swapcontext(&ctx[1], &ctx[2]);
        printf("3\n");
    }
}

void fun3(){
    while (count ++ < 30){
        printf("3\n");
        swapcontext(&ctx[2], &ctx[0]);
        printf("6\n");
    }
}

int main(){
    int stack1[2048] = {0};
    int stack2[2048] = {0};
    int stack3[2048] = {0};
    
    getcontext(&ctx[0]);
    ctx[0].uc_stack.ss_sp = stack1;
    ctx[0].uc_stack.ss_size = sizeof(stack1);
    ctx[0].uc_link = &main_ctx;
    makecontext(&ctx[0], fun1, 0);

    getcontext(&ctx[1]);
    ctx[1].uc_stack.ss_sp = stack2;
    ctx[1].uc_stack.ss_size = sizeof(stack2);
    ctx[1].uc_link = &main_ctx;
    makecontext(&ctx[1], fun2, 0);

    getcontext(&ctx[2]);
    ctx[2].uc_stack.ss_sp = stack3;
    ctx[2].uc_stack.ss_size = sizeof(stack3);
    ctx[2].uc_link = &main_ctx;
    makecontext(&ctx[2], fun3, 0);

    printf("swapcontext\n");
    swapcontext(&main_ctx, &ctx[0]);

    printf("\n");

}

课程地址: www.github.com/0voice