协程

什么是协程

协程是计算机程序组件,它通过允许多个入口点在某些位置暂停和恢复执行来概括用于非抢占式多任务的子程序。协程的思想本质上就是控制流的主动让出和恢复机制。

协程(coroutine)和子程序(subroutines)的区别

子程序是协程的特殊情况。当子程序被调用,则开始执行,当子程序结束,则停止执行。子程序只返回一次,在调用之间不保持状态。协程A可以通过调用协程B退出执行,并且后续可以恢复协程A的执行,即协程实例保持状态,并且在调用之间变化。

协程(coroutine)和生成器(generators)的区别

生成器主要用于简化迭代器的写入,生成器中的yield语句不指定要跳转到的协程,而是将值传递回父例程。

协程的应用

  • Communicating sequential processes where each sub-process is a coroutine. Channel inputs/outputs and blocking operations yield coroutines and a scheduler unblocks them on completion events.
    TODO(caikun): 补充其他的示例

什么时候使用协程

TODO

协程示例

var q := new queue

coroutine produce
    loop
        while q is not full
            create some new items
            add the items to q
        yield to consume

coroutine consume
    loop
        while q is not empty
            remove some items from q
            use the items
        yield to produce

协程的实现

Linux系统对用户上下文管理以及切换的支持

man getcontext
getcontext, setcontext - get or set the user context
int getcontext(ucontext_t *ucp);
int setcontext(const ucontext_t *ucp);
getcontext将当前活动上下文信息存入ucp
setcontext恢复ucp指向的上下文信息

示例

#include 
#include 
#include 

int main(int argc, const char *argv[]){
    ucontext_t context;

    getcontext(&context);
    puts("Hello world");
    sleep(1);
    setcontext(&context);
    return 0;
}
// output are following
Hello world
Hello world
... (repeated every second)

解释:

  1. getcontext将当前活跃上下文存入context
  2. 输出Hello world,并睡眠一秒
  3. setcontext恢复context,即从新回到puts("Hello world")语句
  4. 一直重复2, 3步,永不退出
man makecontext
makecontext, swapcontext - manipulate user context
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
makecontext会修改ucp指向的上下文信息(ucp是通过getcontext获取的上下文信息)。在调用makecontext之前,需要设置好ucp->uc_stack,ucp->uc_link等信息。ucp->uc_stack指向的栈空间,用于新的用户上下文存储寄存器,临时变量等;当ucp指向的上下文执行结束时,即调度到ucp->uc_link指向的context。如果该context为NULL,则进程结束。
swapcontext保存当前的上下文到oucp,并切换到ucp指向的上下文。

示例

#include 
#include 
#include 

static ucontext_t uctx_main, uctx_func1, uctx_func2;

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

static void
func1(void)
{
    printf("func1: started\n");
    printf("func1: swapcontext(&uctx_func1, &uctx_func2)\n");
    if (swapcontext(&uctx_func1, &uctx_func2) == -1)
        handle_error("swapcontext");
    printf("func1: returning\n");
}

static void
func2(void)
{
    printf("func2: started\n");
    printf("func2: swapcontext(&uctx_func2, &uctx_func1)\n");
    if (swapcontext(&uctx_func2, &uctx_func1) == -1)
        handle_error("swapcontext");
    printf("func2: returning\n");
}

int
main(int argc, char *argv[])
{
    char func1_stack[16384];
    char func2_stack[16384];

    if (getcontext(&uctx_func1) == -1)
        handle_error("getcontext");
    uctx_func1.uc_stack.ss_sp = func1_stack;
    uctx_func1.uc_stack.ss_size = sizeof(func1_stack);
    uctx_func1.uc_link = &uctx_main;
    makecontext(&uctx_func1, func1, 0);

    if (getcontext(&uctx_func2) == -1)
        handle_error("getcontext");
    uctx_func2.uc_stack.ss_sp = func2_stack;
    uctx_func2.uc_stack.ss_size = sizeof(func2_stack);
    /* Successor context is f1(), unless argc > 1 */
    uctx_func2.uc_link = (argc > 1) ? NULL : &uctx_func1;
    makecontext(&uctx_func2, func2, 0);

    printf("main: swapcontext(&uctx_main, &uctx_func2)\n");
    if (swapcontext(&uctx_main, &uctx_func2) == -1)
        handle_error("swapcontext");

    printf("main: exiting\n");
    exit(EXIT_SUCCESS);
}
// output are following
./test
main: swapcontext(&uctx_main, &uctx_func2)
func2: started
func2: swapcontext(&uctx_func2, &uctx_func1)
func1: started
func1: swapcontext(&uctx_func1, &uctx_func2)
func2: returning
func1: returning
main: exiting

./test 1
main: swapcontext(&uctx_main, &uctx_func2)
func2: started
func2: swapcontext(&uctx_func2, &uctx_func1)
func1: started
func1: swapcontext(&uctx_func1, &uctx_func2)
func2: returning

解释:

  1. uctx_func1用的栈空间为func1_stack,并且后继上下文为uctx_main
  2. uctx_func2用的栈空间为func2_stack,并且后继上下文为uctx_func1或者NULL
  3. 输出 main: swapcontext(&uctx_main, &uctx_func2)
  4. swapcontext切换到uctx_func2指向的上下文,并将当前的上下文保存在uctx_main
  5. 输出func2: started
  6. 输出func2: swapcontext(&uctx_func2, &uctx_func1)
  7. swapcontext切换到uctx_func2指向的上下文
  8. 输出func1: started
  9. 输出func1: swapcontext(&uctx_func1, &uctx_func2)
  10. 切换回uctx_func2指向的上下文
  11. 输出 func2: returning
  12. 运行 uctx_func2.uc_link指向的context
  13. 若uctx_func2.uc_link == NULL, 则程序退出
  14. 若 uctx_func2.uc_link != NULL, 即在程序中其指向uctx_func1,则恢复指向uctx_func1指向的上下文
  15. 输出 func1: returning
  16. 执行uctx_func1.uc_link指向的上下文,即uctx_main
  17. 输出 main: exiting,程序退出

注:在这个示例程序中,uc_stack是context用到的栈空间,设置为16KB;线程最小stack空间定义在pthread.h中,由常量PTHREAD_STACK_MIN指定,Debian GNU/Linux 8上为#define PTHREAD_STACK_MIN 16384.

剖析云风协程库

参考文章

  • Coroutine wikipedia
  • user context manipulation

你可能感兴趣的:(协程)