协程

2020-08-31  本文已影响0人  wayyyy

以下全文基本是以下几篇文章搬运整合,只做自己记录学习使用,未及时获得作者授权,侵删。

进程
线程

为什么需要线程?或者说线程有什么优点?

协程的优点
x86-64 函数模型
ucontext簇函数
云风协程库示例
协程调度器
struct schedule {
    char stack[STACK_SIZE]; // 运行时栈,此栈即是共享栈
    ucontext_t main; // 主协程的上下文
    int nco;        // 当前存活的协程个数
    int cap;        // 协程管理器的当前最大容量,即可以同时支持多少个协程。如果不够了,则进行2倍扩容
    int running;    // 正在运行的协程ID
    struct coroutine **co; // 一个一维数组,用于存放所有协程。其长度等于cap
};
协程的创建
struct coroutine {
    coroutine_func func; // 协程所用的函数
    void *ud;  // 协程参数
    ucontext_t ctx; // 协程上下文
    struct schedule * sch; // 该协程所属的调度器
    ptrdiff_t cap;   // 已经分配的内存大小
    ptrdiff_t size; // 当前协程运行时栈,保存起来后的大小
    int status; // 协程当前的状态
    char *stack; // 当前协程的保存起来的运行时栈
};
coroutine_resume(READY -> RUNNING)
void coroutine_resume(struct schedule * S, int id) {
    assert(S->running == -1);
    assert(id >=0 && id < S->cap);

    // 取出协程
    struct coroutine *C = S->co[id];
    if (C == NULL)
        return;

    int status = C->status;
    switch(status) {
        case COROUTINE_READY:
            // 初始化ucontext_t结构体,将当前的上下文放到C->ctx里面
            getcontext(&C->ctx);
            // 将当前协程的运行时栈的栈顶设置为S->stack,每个协程都这么设置,这就是所谓的共享栈。(注意,这里是栈顶)
            C->ctx.uc_stack.ss_sp = S->stack; 
            C->ctx.uc_stack.ss_size = STACK_SIZE;
            C->ctx.uc_link = &S->main; // 如果协程执行完,将切换到主协程中执行
            S->running = id;
            C->status = COROUTINE_RUNNING;

            // 设置执行C->ctx函数, 并将S作为参数传进去
            uintptr_t ptr = (uintptr_t)S;
            makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));

            // 将当前的上下文放入S->main中,并将C->ctx的上下文替换到当前上下文
            swapcontext(&S->main, &C->ctx);
            break;
        case COROUTINE_SUSPEND:
            // 将协程所保存的栈的内容,拷贝到当前运行时栈中
            // 其中C->size在yield时有保存
            memcpy(S->stack + STACK_SIZE - C->size, C->stack, C->size);
            S->running = id;
            C->status = COROUTINE_RUNNING;
            swapcontext(&S->main, &C->ctx);
            break;
        default:
            assert(0);
    }
}
状态转换机
image.png
共享栈

共享栈这个词在libco中提到的多,其实coroutine也是用的共享栈模型。 共享栈这个东西说起来很玄乎,实际原理不复杂,本质就是所有的协程在运行的时候都使用同一个栈空间。

共享栈对标的是非共享栈,也就是每个协程的栈空间都是独立的,固定大小。好处是协程切换的时候,内存不用拷贝来拷贝去。坏处则是内存空间浪费.

因为栈空间在运行时不能随时扩容,为了防止栈内存不够,所以要预先每个协程都要预先开一个足够的栈空间使用。当然很多协程用不了这么大的空间,就必然造成内存的浪费。

共享栈则是提前开了一个足够大的栈空间(coroutine默认是1M)。所有的栈运行的时候,都使用这个栈空间。 conroutine是这么设置每个协程的运行时栈:

C->ctx.uc_stack.ss_sp = S->stack;
C->ctx.uc_stack.ss_size = STACK_SIZE;
对协程调用yield的时候,该协程栈内容暂时保存起来,保存的时候需要用到多少内存就开多少,这样就减少了内存的浪费。(即_save_stack函数的内容)。 当resume该协程的时候,协程之前保存的栈内容,会被重新拷贝到运行时栈中。

这就是所谓的共享栈的原理。

上一篇 下一篇

猜你喜欢

热点阅读