操作系统——协程

一、前言

自从换工作以后,已经少有业务学习技术的时间了,对于大的知识点的积累和更新变得比较困难。而本文的知识点——了解协程 适合作为一个在紧凑的工作生活中学习的技术。协程本身不难,但是要了解其中精髓并写出好用的协程应用是需要花费一定时间的。本文将初步的讲解协程的基础知识,并结合一段开源代码进行讲解,最后会通过修改代码完成一定的功能修改,尽量使读者可以基本了解协程

本文前置知识:

  1. 进程
  2. 线程
  3. 上下文切换

二、正文

2.1 传统调用

在了解 协程 之前,我们需要了解一下传统执行流中是如何完成我们的任务的。
通常来说,函数 或者 称为 子程序、子例程 都是层级调用的,比如 下面的伪代码中:

int A()
{
    a()
}
int func()
{
    A()
    B()
    C()
}

如果上述代码使用 线程 进行调用函数 func(),那么就会是:

  1. 调用 A(),进入 A() 执行 a() ,然后返回
  2. 调用 B() 后返回
  3. 调用 C() 返回

上述函数如果在执行过程中被系统切换出去,回来继续从原来的地方继续执行。其 调用顺序是明确的

2.2 协程调用

结合 进程线程 ,其各自的基本概念如下:

  1. 进程:指计算机中已运行的 程序程序 是 指令和数据及其组织形式的描述。在以前的描述描述中通常会有 进程是操作系统资源调度的基本单位,但在现在的操作系统中,进程不再是执行的基本单位,而是 线程的容器。相较于 线程协程进程 拥有最多的资源,比如 文件内存
  2. 线程:是 操作系统资源调度的基本单位。一个线程是一个进程中的一个单一执行流。统一进程中的线程共享该进程的所有资源
  3. 协程:又称为 用户态线程,用于完成 协作式多任务子程序,允许执行被挂起与被恢复。其本质还是 子例程的上下文

协程可以这样通俗的理解,即 多个函数可以在一个线程中被平等调用,这里的 平等调用 是允许 挂起和恢复,不是传统意义上的函数调用。也就是函数可以在执行到一半的时候,在适当的时机切出去完成其他函数,而这都是在同一个线程中完成。

需要注意的的时:
1. 通常 协程 主动在合适的时机通过主动让出的方式让其他协程得以执行
2. 如果没有主动让出并完成后退出函数。会按照调度算法调度下一个协程执行
3. 具体如何完成协程的退出需要看具体实现

使用代码说明的话,其更像是:

int func()
{
    A()/B()/C()
}

上述这段奇怪的代码表示的是:

  1. func() 中可以即执行 A(),也执行 B()C(),他们的之间没有明确的调用顺序
  2. A() 执行到中途时,可以在适当的时机切换为执行 B()C()
  3. B()C() 执行完成或者再某个适当的时机再切换为执行 A()
  4. B()C() 同理

以上的过程是在同一个线程中完成,不会涉及到线程的切换,减少了系统开销。

熟悉 GO语言 的读者应该对协程会比较熟悉,在 高IO 场景下,协程可以在 低系统切换开销 的情况下有效的执行多个 子程序,从而优化整体性能。

2.3 代码示例

笔者使用一个比较简单的开源协程库进行说明,以便读者理解协程。
前提说明:

  1. 该代码使用 POSIXucontext函数族 进行实现,读者可以自行查阅该函数族的相关资料。
  2. 在该代码中可以简单理解 ucontext函数族 提供了 上下文切换的条件

下面的协程的实现讲解

/*---------------------coroutine.h---------------------*/
#ifndef C_COROUTINE_H
#define C_COROUTINE_H

#define COROUTINE_DEAD 0
#define COROUTINE_READY 1
#define COROUTINE_RUNNING 2
#define COROUTINE_SUSPEND 3

struct schedule;

typedef void (*coroutine_func)(struct schedule *, void *ud);

struct schedule * coroutine_open(void);
void coroutine_close(struct schedule *);

int coroutine_new(struct schedule *, coroutine_func, void *ud);
void coroutine_resume(struct schedule *, int id);
int coroutine_status(struct schedule *, int id);
int coroutine_running(struct schedule *);
void coroutine_yield(struct schedule *);

#endif


/*---------------------coroutine.c---------------------*/
struct coroutine;

/* 协程调度器 */
struct schedule {
    char stack[STACK_SIZE]; //栈,用于协程运行时使用
    ucontext_t main;//主协程
    int nco;//协程数量
    int cap;//协程调度器允许创建的最多协程数量
    int running;//当前运行的协程id
    struct coroutine **co;//数组指针,用于指向协程实例数组
};

/* 协程 */
struct coroutine {
    coroutine_func func; //协程执行的子程序或函数
    void *ud;//函数变量
    ucontext_t ctx;//上下文
    struct schedule * sch;//该协程所在的调度器
    ptrdiff_t cap; //协程栈的容量
    ptrdiff_t size; //协程栈的大小,一般和cap相等
    int status; //协程的状态
    char *stack;//协程的栈,用于协程保存栈使用,并不是再运行时使用,主要功能是保存协程的栈
};

/* 创建协程实例 */
struct coroutine * 
_co_new(struct schedule *S , coroutine_func func, void *ud) {
    /* 创建协程所需要的实例 */
    struct coroutine * co = malloc(sizeof(*co));
    /* 初始化各项基本成员 */
    co->func = func;
    co->ud = ud;
    co->sch = S;
    co->cap = 0;
    co->size = 0;
    co->status = COROUTINE_READY; 
    co->stack = NULL;
    return co;
}

/* 删除协程实例 */
void
_co_delete(struct coroutine *co) {
    /* 
      协程在调度过程中可能会开辟栈,所以这里需要释放,
      相关内容需要在下面的代码中才能进行讲解 
    */
    free(co->stack);
    /* 释放协程 */
    free(co);
}

/* 打开协程调度器,通过调用该函数的为主协程 */
struct schedule * 
coroutine_open(void) {
    /* 开辟调度器 */
    struct schedule *S = malloc(sizeof(*S));
    /* 初始化调度器成员 */
    S->nco = 0;
    S->cap = DEFAULT_COROUTINE;//DEFAULT_COROUTINE = 16
    S->running = -1;//running = -1指当前为主协程在执行
    S->co = malloc(sizeof(struct coroutine *) * S->cap);//开辟协程数组
    memset(S->co, 0, sizeof(struct coroutine *) * S->cap);//清空协程数组
    return S;
}

/* 关闭协程调度器 */
void 
coroutine_close(struct schedule *S) {
    int i;
    /* 删除所有协程 */
    for (i=0;icap;i++) {
        struct coroutine * co = S->co[i];
        if (co) {
            _co_delete(co);
        }
    }
    /* 删除协程数组 */
    free(S->co);
    S->co = NULL;
    /*  释放调度器 */
    free(S);
}

/* 
    创建协程 
    S :协程调度器
    func:协程需要执行的函数
    ud:函数参数
*/
int 
coroutine_new(struct schedule *S, coroutine_func func, void *ud) {
    /* 创建协程实例 */
    struct coroutine *co = _co_new(S, func , ud);
    /* 
      查看当前协程的数量是否达到上限 
      如果到达上限,则扩大容量为原来的2倍,并将新创建的协程放在其中
      最后返回协程的id
    */
    if (S->nco >= S->cap) {
        int id = S->cap;
        S->co = realloc(S->co, S->cap * 2 * sizeof(struct coroutine *));
        memset(S->co + S->cap , 0 , sizeof(struct coroutine *) * S->cap);
        S->co[S->cap] = co;
        S->cap *= 2;
        ++S->nco;
        return id;
    } else {
    /*
      如果协程数量没有超过上限,则将协程添加到调度器中,并返回协程的id
    */
        int i;
        for (i=0;icap;i++) {
            int id = (i+S->nco) % S->cap;
            if (S->co[id] == NULL) {
                S->co[id] = co;
                ++S->nco;
                return id;
            }
        }
    }
    assert(0);
    return -1;
}

/*
    协程的主要执行函数,协程并不是执行执行其函数的,一般都在mainfunc中执行
    在协程执行前后需要做一些处理
    low32:调度器地址的低32bit
    high:调度器地址的高32bit
*/
static void
mainfunc(uint32_t low32, uint32_t hi32) {
    /* 获取调度器 */
    uintptr_t ptr = (uintptr_t)low32 | ((uintptr_t)hi32 << 32);
    struct schedule *S = (struct schedule *)ptr;
    /* 获取当前调度需要执行的协程id,并通过id获取协程的实例 */  
    int id = S->running;
    struct coroutine *C = S->co[id];
    /* 执行协程的函数 */
    C->func(S,C->ud);
    /* 由于协程没有主动让出并退出了函数,这里需要对协程进行回收 */
    _co_delete(C);//删除协程实例
    S->co[id] = NULL;//将协程在调度器中去掉
    --S->nco;//减少调度器的协程数量
    /*
      1. 使用ucontext函数族实现时上文下切换,当前上下文退出后会切换到指定的上下文
      2. 该调度器的实现是需要从主线程切换到其他协程,所以当协程退出后会返回主协程
      3. 将当前执行的协程id修改为-1,表示当前是主协程执行
    */
    S->running = -1;
}

/* 
    恢复协程,该函数通常在主协程中使用
    S:协程调度器
    id:需要恢复执行的协程id
*/
void 
coroutine_resume(struct schedule * S, int id) {
    assert(S->running == -1);
    assert(id >=0 && id < S->cap);
    /* 获取需要恢复执行的协程的id并判断其有效性 */
    struct coroutine *C = S->co[id];
    if (C == NULL)
        return;
    /* 获取协程的状态,并根据状态使用不同的操作 */
    int status = C->status;
    switch(status) {
    /* 
      协程完成创建时状态为COROUTINE_READY 
      此时协程的上下文并没有完成初始化,所以需要特殊处理
    */
    case COROUTINE_READY:
        /* 初始化协程上下文 */
        getcontext(&C->ctx);
        C->ctx.uc_stack.ss_sp = S->stack;//指定协程的栈为使用调度的栈成员
        C->ctx.uc_stack.ss_size = STACK_SIZE;//指定协程栈的大小
        C->ctx.uc_link = &S->main;//指定协程退出后返回到主协程,与mainfunc中的操作相呼应
        S->running = id;//声明当前执行的协程id
        C->status = COROUTINE_RUNNING;//将协程的状态设置为RUNNING
        /* 
          使用makecontext创建协程上下文 
          1. 协程的执行函数为mainfunc
          2. 指定mainfunc的参数的数量为2,分别为调度器的指针的高低32bit
        */
        uintptr_t ptr = (uintptr_t)S;//
        makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));
          /* 
            使用swapcontext完成上下文的切换
            1. 该函数会将当前的上下文保存在S->main中,该函数通常在主线程中执行,所以S->main也就保存了当前的上下文
            2. 将当前的上下文切换到C->ctx,即刚刚构造的上下文中
            3. 下一步会跳到刚刚指定的mainfunc中执行协程的func成员
          */
        swapcontext(&S->main, &C->ctx);
        break;
    /*
      协程在挂起时状态为COROUTINE_SUSPEND,此时协程已经有了上下文,但是被挂起,现在需要恢复执行
    */
    case COROUTINE_SUSPEND:
        /* 将协程的保存的栈设置到调度器S的栈成员中,因为在协程上下文初始化时指定的栈是调度器S的stack成员 */
        memcpy(S->stack + STACK_SIZE - C->size, C->stack, C->size);
        S->running = id;//声明当前执行的协程id为恢复执行协程的id
        C->status = COROUTINE_RUNNING;//将协程的状态设置为RUNNING
        swapcontext(&S->main, &C->ctx);//切换协程
        break;
    default:
        assert(0);
    }
}

/* 
    保存协程的栈,通常在协程的让出函数中调用,用于保存协程当前执行的栈
    C:协程
    top:协程的栈底指正,使用top表示的是地址的top,因为栈是向上增长的,所以方向反了
*/
static void
_save_stack(struct coroutine *C, char *top) {
    /* dummp为当前协程的栈顶变量,通过dummu可以获取当前的栈顶指针 */
    char dummy = 0;
    assert(top - &dummy <= STACK_SIZE);
    /* 如果当前的栈顶是否已经超出当前栈的容量 */
    if (C->cap < top - &dummy) {
        free(C->stack);//释放当前协程的保存栈
        C->cap = top-&dummy;//获取当前栈使用的大小
        C->stack = malloc(C->cap);//根据大小重新分配保存栈
    }
    C->size = top - &dummy;//设置保存栈大小,也就是当前栈使用的情况
    memcpy(C->stack, &dummy, C->size);//将栈保存到协程的stack成员中,在这里完成了栈的保存
}

/*
  协程让出函数,通常协程自己调用,用于当前协程让出给其他协程使用,在该实现中是让出到主协程
  S:协程调度器
*/
void
coroutine_yield(struct schedule * S) {
    /* 获取当前运行的协程id */
    int id = S->running;
    assert(id >= 0);
    /* 获取当前协程实例 */
    struct coroutine * C = S->co[id];
    assert((char *)&C > S->stack);
    /* 将当前的运行栈保存到协程中 */
    _save_stack(C,S->stack + STACK_SIZE);
    /* 将协程状态置为挂起 */
    C->status = COROUTINE_SUSPEND;
    /* 声明运行协程id */
    S->running = -1;
    /* 切换上下文到主协程 */
    swapcontext(&C->ctx , &S->main);
}

/* 获取指定id协程的状态 */
int 
coroutine_status(struct schedule * S, int id) {
    assert(id>=0 && id < S->cap);
    if (S->co[id] == NULL) {
        return COROUTINE_DEAD;
    }
    return S->co[id]->status;
}

/* 获取当前运行的协程id*/
int 
coroutine_running(struct schedule * S) {
    return S->running;
}

上面主要讲解了协程的实现,但关看实现可能还无法完全了解协程是如何运行的,此时还无法将协程的整个声明周期组织起来
下面讲解一下该协程库的demo,方便读者理解

#include "coroutine.h"
#include 

struct args {
    int n;
};

/* 协程的执行函数 */
static void
foo(struct schedule * S, void *ud) {
    struct args * arg = ud;
    int start = arg->n;
    int i;
    for (i=0;i<5;i++) {
      /* 打印当前协程的id和变量 */
        printf("coroutine %d : %d\n",coroutine_running(S) , start + i);
        /* 让出协程 */
        coroutine_yield(S);
    }
}

/* 测试函数 */
static void
test(struct schedule *S) {
    struct args arg1 = { 0 };
    struct args arg2 = { 100 };
    /* 
      1. 创建2个协程,并传入相同的执行函数 
      2. 传入不同的变量,用于区分协程
    */
    int co1 = coroutine_new(S, foo, &arg1);
    int co2 = coroutine_new(S, foo, &arg2);
    printf("main start\n");
    /* 
      1. 判断2个协程的状态,如果为COROUTINE_DEAD(0)则说明协程已经退出 
      2. 至于协程退出可以看上一章节中的mainfunc函数讲解
    */
    while (coroutine_status(S,co1) && coroutine_status(S,co2)) {
        /* 如果没有退出则恢复当前协程执行 */
        printf("step 1\n");
        coroutine_resume(S,co1);
        printf("step 2\n");
        coroutine_resume(S,co2);
        printf("step 3\n");
    } 
    printf("main end\n");
}

int 
main() {
    struct schedule * S = coroutine_open();
    test(S);
    coroutine_close(S);
    
    return 0;
}

执行结果为:


修改

从demo可以看出几点:
1. 每次执行完协程后都需要返回主协程,并由主协程继续恢复下一个协程的运行
2. 从而可以看出该库的协程的顺序是明确的

2.4 代码修改

2.3章节 中我们可以了解到协程的的运行,但该实现会造成2个问题:
1.每次协程让出都需要回到主协程才能继续下一个协程,造成不必要的切换,因为主协程本身没有做任何事情
2. 协程的执行顺序由主协程指定,无法根据调度算法进行切换

基于上面2个原因,笔者修改代码以实现下面2点:
1. 协程让出或者退出后不回到主协程,由调度器查找下一个运行的协程并执行
2. 运行的协程有调度算法决定

下面的代码修改和讲解

/*---------------------coroutine_v2.h---------------------*/
#ifndef C_COROUTINE_H
#define C_COROUTINE_H

#define COROUTINE_DEAD 0
#define COROUTINE_READY 1
#define COROUTINE_RUNNING 2
#define COROUTINE_SUSPEND 3

struct schedule;

typedef void (*coroutine_func)(struct schedule *, void *ud);

struct schedule * coroutine_open(void);
void coroutine_close(struct schedule *);

/* 相比于上面版本,少了resum恢复函数,只有yield让出函数 */
int coroutine_new(struct schedule *, coroutine_func, void *ud);
int coroutine_status(struct schedule *, int id);
int coroutine_running(struct schedule *);
void coroutine_yield(struct schedule *);

#endif

/*---------------------coroutine_v2.c---------------------*/
#include "coroutine.h"
#include 
#include 
#include 
#include 
#include 
#include 

#if __APPLE__ && __MACH__
    #include 
#else
    #include 
#endif

#define STACK_SIZE (1024*1024)
#define DEFAULT_COROUTINE 16

/* 协程 */
struct coroutine
{
    coroutine_func func;//协程执行函数
    void *ud;//函数变量
    ucontext_t ctx;//协程上下文
    struct schedule * sch;//协程所在的调度器
    ptrdiff_t cap;//协程栈的容量
    ptrdiff_t size;//协程栈的大小,一般于cap相等
    int status;//协程状态
    char* stack;//协程的栈,该栈是运行时使用。并不是之前是保存时使用
};

/*
    协程调度器
    可以看出和之前相比少了许多成员
*/
struct schedule
{
    int nco;//协程数量
    int cap;//调度器的协程最大数量
    int running;//当前运行的协程id
    ucontext_t main;//主协程上下文
    struct coroutine **co;//数组指针,指向协程数组
};

/* 删除协程 */
void co_delete(struct coroutine *co)
{
    /*
        1. 与之前的实现不同,这里删除协程修改为将协程的状态置为COROUTINE_DEAD
        2. 使用该状态以让协程在调度时不会被选择
    */
    co->status = COROUTINE_DEAD;
}

/* 查找下一个可以执行的协程 */
static int find_next_co(struct schedule *S, int id)
{
    /*
        1. 找到下一个存在切状态为挂起或者ready的协程
        2. 状态为COROUTINE_DEAD的协程不会被查找到
    */
    for (int i = id; i < S->nco; i++)
    {
        if (NULL == S->co[i])
        {
            continue;
        }

        if (COROUTINE_SUSPEND == S->co[i]->status || COROUTINE_READY == S->co[i]->status)
        {
            return i;
        }
    }

    /* 如果没有满足的协程的返回-1,回到主协程 */
    return -1;
}

/*
  协程的主要执行函数,在该函数中执行协程中的func成员
*/
static void mainfunc(uint32_t low32, uint32_t hi32)
{
    uintptr_t ptr = (uintptr_t)low32 | ((uintptr_t)hi32 << 32);
    struct schedule *S = (struct schedule *)ptr;
    int id = S->running;
    struct coroutine *C = S->co[id];
    struct coroutine *next_C = NULL;
    C->func(S,C->ud);
    /*
      与之前的实现不同,这里对于协程退出的处理做了修改
      1. co_delete没有直接删除协程,只是修改了状态
      2. 因为这里还在协程的上下文中,此时删除协程会出错
      3. 查找下一个执行的协程
      4. 如果协程调度器没有需要执行的协程了,则直接返回主协程
    */
    co_delete(C);
    id = find_next_co(S, id);
    if (-1 == id)
    {
        /* 如果协程调度器没有需要执行的协程了,则直接返回主协程 */
        S->running = -1;
        setcontext(&S->main);
    }
    else
    {
        /* 执行下一个协程 */
        S->running = id;
        next_C = S->co[id];
        setcontext(&next_C->ctx);
    }
}

struct coroutine *co_new(struct schedule *S , coroutine_func func, void *ud)
{
    struct coroutine* co = malloc(sizeof(*co));
    co->func = func;
    co->ud = ud;
    co->sch = S;
    co->cap = STACK_SIZE;
    co->size = STACK_SIZE;
    co->status = COROUTINE_READY;
    co->stack = malloc(STACK_SIZE);//分配协程的栈,这里主要是用于协程执行时使用

    /*
      初始化协程上下文
      与之前相比:
      1. 协程上下文的初始化在创建协程时完成,不在resum函数中执行
      2. 协程上下文中运行的栈改为协程的stack成员,而不是之前调度器中stack成员
      这样做的好处是不需要在调度时保存栈,直接切换上下文即可
    */
    getcontext(&co->ctx);
    co->ctx.uc_stack.ss_sp = co->stack;
    co->ctx.uc_stack.ss_size = STACK_SIZE;
    co->ctx.uc_link = &S->main;
    /* 创建协程上下文 */
    uintptr_t ptr = (uintptr_t)S;
    makecontext(&co->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));

    return co;
}

/* 打开协程调度器,与之前的实现一样 */
struct schedule* coroutine_open(void)
{
    struct schedule *S = malloc(sizeof(*S));
    S->nco = 0;
    S->cap = DEFAULT_COROUTINE;
    S->running = -1;
    S->co = malloc(sizeof(struct coroutine *) * S->cap);
    memset(S->co, 0, sizeof(struct coroutine *) * S->cap);
    return S;
}

/* 关闭协程调度器 */
void coroutine_close(struct schedule *S)
{
    int i;
    for (i = 0; i < S->cap; i++)
    {
        struct coroutine* co = S->co[i];
        if (co)
        {
            /* 删除协程及其栈 */
            free(co->stack);
            free(co);
        }
    }
    free(S->co);
    S->co = NULL;
    free(S);
}

/* 创建协程,与之前的实现一样 */
int coroutine_new(struct schedule *S, coroutine_func func, void *ud)
{
    struct coroutine *co = co_new(S, func , ud);
    if (S->nco >= S->cap)
    {
        int id = S->cap;
        S->co = realloc(S->co, S->cap * 2 * sizeof(struct coroutine *));
        memset(S->co + S->cap , 0 , sizeof(struct coroutine *) * S->cap);
        S->co[S->cap] = co;
        S->cap *= 2;
        ++S->nco;
        return id;
    }
    else
    {
        int i;
        for (i=0;icap;i++)
        {
            int id = (i+S->nco) % S->cap;
            if (S->co[id] == NULL)
            {
                S->co[id] = co;
                ++S->nco;
                return id;
            }
        }
    }
    return -1;
}

/*
    协程让出函数
    1. 与之前的实现不同,这里的yield函数会指定下一个执行的协程
    2. 完成协程的直接切换,不需要回到主协程
*/
void coroutine_yield(struct schedule * S)
{
    int id = S->running;//获取当前执行协程的id
    struct coroutine *C = NULL;
    struct coroutine* next_C = NULL;
    if (0 >= S->nco)
    {
        printf("no routine create\n");
        return;
    }

    /* 如果当前为主协程 */
    if (-1 >= id)
    {
        id = 0;//指定默认协程
        id = find_next_co(S, id);//查找下一个可用
        if (-1 == id)
        {
            /*
                如果找不到则返回,因为当前是主协程,所以不需要切换
            */
            printf("yidld error\n");
            return;
        }
        C = S->co[id];//获取协程的实例
        S->running = id;
        C->status = COROUTINE_RUNNING;
        swapcontext(&S->main, &C->ctx);//切换到第一个协程
    }
    else //如果当前为其他协程
    {
        C = S->co[id];//获取当前协程
        C->status = COROUTINE_SUSPEND;//将当前协程的状态修改为挂起

        id = find_next_co(S, id);//查找下一个可用
        if (-1 == id)
        {
            /*
                如果找不到可执行协程,则返回主协程
            */
            setcontext(&S->main);
        }
        next_C = S->co[id];//获取下一个协程实例
        next_C->status = COROUTINE_RUNNING;//将下一个协程状态置为运行

        S->running = id;//设置运行协程的id
        swapcontext(&C->ctx , &next_C->ctx);//切换到下一个协程
    }
}

/* 获取指定协程状态 */
int coroutine_status(struct schedule * S, int id)
{
    if (S->co[id] == NULL)
    {
        return COROUTINE_DEAD;
    }
    return S->co[id]->status;
}

/* 获取当前运行的协程 */
int coroutine_running(struct schedule * S)
{
    return S->running;
}

上面讲解修改后的代码,下面我们看看具体的使用用例方便读者理解

#include "coroutine_v2.h"
#include 

struct args {
    int n;
};

static void
foo(struct schedule * S, void *ud) {
    struct args * arg = ud;
    int start = arg->n;
    int i;
    for (i=0;i<5;i++) {
        printf("coroutine %d : %d\n",coroutine_running(S) , start + i);
        coroutine_yield(S);
    }
}

static void
test(struct schedule *S) {
    struct args arg1 = { 0 };
    struct args arg2 = { 100 };
    /* 创建2个协程 */
    int co1 = coroutine_new(S, foo, &arg1);
    int co2 = coroutine_new(S, foo, &arg2);
    printf("main start\n");
    /* 判断协程状态是否正常 */
    while (0 != coroutine_status(S,co1) || 0 != coroutine_status(S,co2)) {
        /* 让出协程 */
        printf("before yield\n");
        coroutine_yield(S);
        printf("after yield\n");
    }
    printf("main end\n");
}

int
main() {
    struct schedule * S = coroutine_open();
    test(S);
    coroutine_close(S);

    return 0;
}

执行结果如下,可以看到每次调度都没有进入主协程


修改后

PS:以上代码作为demo进行,可能存在bug,欢迎各位读者朋友讨论

2.5 总结

总结一下,协程有以下优点及适用场景如下:
优点:

  1. 在某些场景如生产消费场景,可以简化编程模型
  2. 降低切换开销,因为不需要系统中断和系统进行调度,仅有上下文开销

适用于 高IO场景,典型的有:

  1. 高并发网络编程
  2. 高IO,比如 异构计算、磁盘IO、设备IO

三、参考链接

  1. 参考代码
  2. 维基百科
  3. 协程 以及进程和线程
  4. 协程(coroutine)简介
  5. 上下文切换

你可能感兴趣的:(操作系统——协程)