1. 协程
- 协程可以理解为一种用户态的轻量级线程, 切换由用户定义
- 协程上下文切换很快, 因为不会陷入内核态
- 协程拥有自己的寄存器上下文和栈, 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈
优点
- 协程具有极高的执行效率 因为子程序切换不是线程切换,是由程序自身控制,因此协程没有线程切换的开销, 多线程的线程数量越多,协程的性能优势就越明显
- 访问共享资源不需要多线程的锁机制, 因为只有一个线程, 也不存在同时写变量冲突, 所以在协程中控制共享资源无需加锁, 只需要判断状态就好了,执行效率比多线程高很多, 而且代码编写难度也可以相应降低
- 以同步代码的方式写异步逻辑
缺点
- 无法利用多核资源, 除非和多进程配合
2. 了解ucontex
- ucontext组件是什么
- 头文件
<ucontext.h>
定义了两个数据结构,mcontext_t
(暂时用不到)和ucontext_t
和四个函数, 可以被用来实现一个进程中的用户级线程(协程)切换
- 头文件
数据结构
ucontext_t
结构体至少拥有如下几个域typedef struct ucontext { struct ucontext *uc_link; sigset_t uc_sigmask; stack_t uc_stack; mcontext_t uc_mcontext; ... } ucontext_t;
uc_link
指向后继上下文, 当前上下文运行终止时系统会恢复指向的上下文uc_sigmask
为该上下文中的阻塞信号集合uc_stack
为该上下文中使用的栈uc_mcontex
保存上下文的特定机器, 包括调用线程的特定寄存器等等- 简而言之这个数据结构是用来保存上下文的
函数
-
int getcontext(ucontext_t * ucp);
- 获取当前上下文, 初始化ucp结构体, 将当前上下文保存到ucp中
- 成功返回0; 失败返回-1, 并设置errno
-
void makecontext(ucontext_t *ucp, void(*func)(), int argc, ...);
- 创建一个目标上下文 创建方式: (1) getcontext, (2) 指定分配给上下文的栈
uc_stack.ss_sp
, (3) 指定这块栈的大小uc_stack.ss_size
, (4) 指定uc_stack.ss_flags
, (5) 指定后继上下文uc_link
- 协程运行时使用主协程划分的栈空间,而协程切回主线程时需要将该部分栈空间的内容copy到每个协程各自的一个空间缓存起来,因为主协程中划分的栈空间并不是只用于一个协程,而是会用于多个协程
- makecontext可以修改通过getcontext初始化得到的上下文, (必须先调用getcontext), 然后为ucp指定一个栈空间
ucp->stack
, 设置后继的上下文ucp->uc_link
- 当上下文通过setcontext或者swapcontext激活后, 执行func函数(argc为后续的参数个数, 可变参数). 当func执行返回后, 继承的上下文被激活(
ucp->uc_link
), 如果为NULL, 则线程退出
ucontext_t tar_ctx; ucontext_t next_ctx; char stack[100]; getcontext(&tar_ctx); tar_ctx.uc_stack.ss_sp = stack; tar_ctx.uc_stack.ss_size = sizeof(stack); tar_ctx.uc_stack.ss_flags = 0; tar_ctx.uc_link = &next_ctx;
- 创建一个目标上下文 创建方式: (1) getcontext, (2) 指定分配给上下文的栈
-
int setcontext(const ucontext_t *ucp);
- 设置当前的上下文为ucp(激活ucp)
- ucp来自getcontext, 那么上下文恢复至ucp
- ucp来自makecontext, 那么将会调用makecontext函数的第二个参数指向的函数func, 如果func返回, 则恢复至
ucp->uc_link
指定的后继上下文, 如果该ucp中的uc_link
为NULL, 那么线程退出 - 成功不返回, 失败返回-1, 设置errno
-
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);
- 切换上下文
- 保存当前上下文至oucp, 激活ucp上下文(先执行makecontext指定的ucp入口函数, 而后会执行
ucp->uc_link
指向的后继上下文) - 成功不返回, 失败返回-1, 设置errno
3、轻量级协程实现
思路
- 基本思路:
- 构造一个协程调度结构体, 用于调度所有的用户协程
- 构造一个用户协程结构体, 该类对象对应每个用户协程
- 用户协程首次激活, 将会为其分配协程调度器提供的栈区stack
- 用户协程被挂起, 那么会将该协程的栈信息由栈区stack保存到其自身的缓存区buffer;
- 用户协程被唤醒, 那么会将该用户协程的栈信息从缓存区buffer中Reload至栈区
- 协程库框架
- 激活 初始化用户协程(指定协程状态RUNNING), 初始化用户协程上下文(指定协程栈空间stack, 指定后继上下文), 将协程加入协程池
- 挂起 将栈空间stack对应的数据缓存至当前用户协程的栈缓存buffer中, 更改协程状态SUSPEND
- 恢复 将用户协程栈缓存buffer中的数据reload进栈空间stack
数据成员
- 主协程上下文
ucontext_t main_ctx
- 协程堆栈, 所有的协程都利用这块区域
成员函数
创建调度器
- schedule_t *schedule_create() ;
创建协程,返回当前协程在调度器的下标
- int coroutine_create(schedule_t *s, void *(*call_back)(schedule_t *, void *args), void *args) ;
启动协程
- void coroutine_running(schedule_t *s, int id);
让出CPU
- void coroutine_yield(schedule_t *s) ;
恢复CPU
- void coroutine_resume(schedule_t *s, int id);
释放调度器
- void schedule_destroy(schedule_t *s) ;
判断是否所有协程都运行完了
- int schedule_finished(schedule_t *s) ;