协程的概念及实现
协程概念
协程是计算机程序的一类组件,允许执行被挂起和恢复。
协程与函数比较
- 函数只有一个入口点和出口点;协程有多个
- 函数可以调用其它函数,调用者等待被调用者结束后继续执行 ,生命周期遵循后进先出;协程的生命周期有他们的使用需要来决定
- 函数一次性返回全部结果;协程可以每次返回部分结果,这种协程称为生成器
协程有多个入口点,执行过程过
协程与线程比较
- 线程是抢占式多任务处理;协程是协作式多任务处理
- 线程能提供并行性,协程只能并发
协程与回调函数比较
- 回调函数异步时编码复杂,可读性差;协程以顺序方式组织异步代码,可读性更好
协程实现
函数的机器级表示 里提到,函数调用时将调用点保存在栈里,函数返回从栈中取出调用点地址,直接跳转回调用点。同时函数的局部变量也保存在栈中。
协程分为有栈协程和无栈协程:
- 有栈协程通过类似函数,为每个协程分配栈空间保存上下文,切换时保存当前栈的完整状态,恢复时恢复另一个协程的栈状态。
- 无栈协程:所有协程在同一个线程栈上运行。挂起时,通过状态机记录状态,将局部变量等数据(延续)保存在堆上,并返回。恢复时,重新调用函数并从保存的状态开始执行.
| 有栈协程 | 无栈协程 | |
|---|---|---|
| 优点 | 任意函数挂起和恢复 | 开销小,不需要栈空间 |
| 缺点 | 需要为每个协程分配栈空间,消耗大 | 只能在语言定义的特定点挂起,比如 await 处 |
有栈协程
有栈协程可以实现为每个协程独立一个栈,或者所有协程共享一个栈,下面实现一个简单的有栈协程
定义协程对象,协程栈大小为 64 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21typedef struct co co_t; typedef void (*co_entry_t)(); #define CO_STACK_SIZE (64 * 1024) // 64KB struct co_ctx { void* rsp; void* rbx; void* rbp; void* r12; void* r13; void* r14; void* r15; }; struct co { co_entry_t entry; struct co_ctx ctx; char stack[CO_STACK_SIZE]; };创建一个协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17co_t* co_create(co_entry_t entry) { co_t* co = malloc(sizeof(co_t)); if (!co) return NULL; co->entry = entry; // 协程的栈顶 char* stack_top = co->stack + CO_STACK_SIZE; stack_top = (char*)((uintptr_t)stack_top & ~0xF); // 将 entry 地址放到栈中 stack_top -= sizeof(void*); *(void**)stack_top = (void*)entry; co->ctx.rsp = stack_top; return co; }内存视图
协程上下文切换
1extern void co_ctx_swap(struct co_ctx* from, struct co_ctx* to);1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25# co_ctx_swap.S .text .globl co_ctx_swap .type co_ctx_swap, @function co_ctx_swap: movq %rsp, (%rdi) movq %rbx, 8(%rdi) movq %rbp, 16(%rdi) movq %r12, 24(%rdi) movq %r13, 32(%rdi) movq %r14, 40(%rdi) movq %r15, 48(%rdi) movq 48(%rsi), %r15 movq 40(%rsi), %r14 movq 32(%rsi), %r13 movq 24(%rsi), %r12 movq 16(%rsi), %rbp movq 8(%rsi), %rbx movq (%rsi), %rsp ret .size co_ctx_swap, .-co_ctx_swap首先将当前上下文保存在第一个参数,然后将切换的上下文赋值给寄存器,最后
ret指令将entry地址弹出并将PC寄存器指向entry协程运行
当前只实现一个协程,因此简单的用
cur_co记录协程让出
协程销毁
测试程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41#include "coroutine.h" #include <stdio.h> void test(void) { int a = 10; printf("test 1... a = %d\n", a); co_yield(); a++; printf("test 2... a = %d\n", a); co_yield(); a++; printf("test 3... a = %d\n", a); co_yield(); } int main(int argc, char *argv[]) { co_t* co = co_create(test); co_resume(co); printf("main 1....\n"); co_resume(co); printf("main 2 ...\n"); co_resume(co); printf("main end\n"); co_destroy(co); return 0; }运行结果
无栈协程
无栈协程使用状态机进行跳转,使用堆内存保存协程状态和局部变量
| |
上面的 test 协程实际上由编译器处理