协程,英文叫做 Coroutine,又称微线程、纤程,协程是一种用户态的轻量级线程。简单地说,协程就是在用户态对线程的模拟,我们都知道线程的调度是由操作系统内核完成的,而协程的调度是由用户代码完成的。
相比线程,协程有如下优势
当然,有优点就有缺点
一个协程库需要对用户提供的最基本的三个功能是
我们都知道,操作系统内核在切换线程时,需要保存以及切换线程的上下文。同样,协程库在切换协程时,也需要保存以及切换协程的上下文。
切换上下文,听起来很麻烦,好在ucontext.h头文件提供了对上下文进行操作的函数,使得我们不必通过汇编代码来操作上下文。
云风的协程库用到了ucontext中的以下四个函数
#include
int getcontext(ucontext_t* ucp);
typedef struct ucontext {
struct ucontext* uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
...
} ucontext_t;
#include
int setcontext(const ucontext_t* ucp);
#include
int makecontext(ucontext_t* oucp, const ucontext_t* ucp);
#include
void makecontext(ucontext_t* ucp, void(*func)(), int argc, ...);
struct coroutine {
coroutine_func func; // 函数指针,指向协程运行的函数
void *ud; // func的函数参数
ucontext_t ctx; // 协程的上下文
struct schedule * sch; // 协程所属的调度器
ptrdiff_t cap; // 协程的私有栈的空间大小
ptrdiff_t size; // 协程的私有栈的已用空间
int status; // 协程状态,可取值为COROUTINE_DEAD、COROUTINE_READY、COROUTINE_RUNNING、COROUTINE_SUSPEND
char *stack; // 协程的私有栈
};
struct schedule {
char stack[STACK_SIZE]; // 协程的公有栈
ucontext_t main; // 调度器的上下文
int nco; // 调度器管理着多少个状态不为COROUTINE_DEAD的协程
int cap; // co数组的大小
int running; // 正在运行的协程的ID
struct coroutine **co; // 保存指向所有协程的指针的一维数组
};
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:
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;
S->running = id;
C->status = COROUTINE_RUNNING;
uintptr_t ptr = (uintptr_t)S;
makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));
swapcontext(&S->main, &C->ctx);
break;
case COROUTINE_SUSPEND:
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);
}
}
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];
C->func(S,C->ud);
_co_delete(C);
S->co[id] = NULL;
--S->nco;
S->running = -1;
}
void
coroutine_yield(struct schedule * S) {
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;
S->running = -1;
swapcontext(&C->ctx , &S->main);
}
static void
_save_stack(struct coroutine *C, char *top) {
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);
}
云风协程库
我把云风协程库改成了C++版本,可读性更强,后续还会添加调度策略,如分时间片调度等