协程

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保存上下文的特定机器, 包括调用线程的特定寄存器等等
    • 简而言之这个数据结构是用来保存上下文的

函数

  1. int getcontext(ucontext_t * ucp);

    • 获取当前上下文, 初始化ucp结构体, 将当前上下文保存到ucp中
    • 成功返回0; 失败返回-1, 并设置errno
  2. 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;
    
  3. 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
  4. 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) ;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值