【sylar-webserver】4 协程模块

43 阅读5分钟

知识点

ucontext

使用 ucontext.h 实现有栈协程(非对称协程)

typedef struct ucontext
  {
    unsigned long int uc_flags;
    struct ucontext *uc_link;//后序上下文
    __sigset_t uc_sigmask;// 信号屏蔽字掩码
    stack_t uc_stack;// 上下文所使用的栈
    mcontext_t uc_mcontext;// 保存的上下文的寄存器信息
    long int uc_filler[5];
  } ucontext_t;

//其中mcontext_t 定义如下
typedef struct
  {
    gregset_t __ctx(gregs);//所装载寄存器
    fpregset_t __ctx(fpregs);//寄存器的类型
} mcontext_t;

//其中gregset_t 定义如下
typedef greg_t gregset_t[NGREG];//包括了所有的寄存器的信息

getcontext()

函数:int getcontext(ucontext_t* ucp) 功能:将当前运行到的寄存器的信息保存在参数ucp中

setcontext()

函数:int setcontext(const ucontext_t *ucp) 功能:将ucontext_t结构体变量ucp中的上下文信息重新恢复到cpu中并执行

makecontext()

函数:void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...) 功能:修改上下文信息,参数ucp就是我们要修改的上下文信息结构体;func是上下文的入口函数;argc是入口函数的参数个数,后面的…是具体的入口函数参数,该参数必须为整形值

swapcontext()

函数:int swapcontext(ucontext_t *oucp, ucontext_t *ucp) 功能:将当前cpu中的上下文信息保存带oucp结构体变量中,然后将ucp中的结构体的上下文信息恢复到cpu中 这里可以理解为调用了两个函数,第一次是调用了getcontext(oucp)然后再调用setcontext(ucp)

ucontext_t m_ctx;

getcontext(&m_ctx);	// 获取上下文信息,保存在 uc_mcontext

m_stack = StackAllocator::Alloc(m_stacksize); // 分配内存

m_ctx.uc_link = nullptr;		// 下一个指向的上下文 任务。置空,所有上下文都显性切换
m_ctx.uc_stack.ss_sp = m_stack;
m_ctx.uc_stack.ss_size = m_stacksize;

makecontext(&m_ctx, &Fiber::MainFunc, 0);	// 指定 上下文 任务

// swapcontext,保存当前上下文到m_ctx,切换到m_target_ctx上下文
swapcontext(&m_ctx, &m_target_ctx);	

设计

协程状态 在这里插入图片描述

  • 避免 错误操作 协程。
  • 根据状态判断当前协程,是中途 yield 还是正常结束 TERM。
/**
 * @brief 协程状态
 * @details 在sylar基础上进行了状态简化,只定义三态转换关系,也就是协程要么正在运行(RUNNING),
 * 要么准备运行(READY),要么运行结束(TERM)。不区分协程的初始状态,初始即READY。不区分协程是异常结束还是正常结束,
 * 只要结束就是TERM状态。也不区别HOLD状态,协程只要未结束也非运行态,那就是READY状态。
*/

enum State {
/// 就绪态,刚创建或者yield之后的状态
    READY,
/// 运行态,resume之后的状态
    RUNNING,
/// 结束态,协程的回调函数执行完之后为TERM状态
    TERM
};

Fiber类

class Fiber : public std::enable_shared_from_this<Fiber> {
public:
    typedef std::shared_ptr<Fiber> ptr;
   
private:
    /**
     * @brief 构造函数
     * @attention 无参构造函数只用于创建线程的第一个协程,也就是线程主函数对应的协程,
     * 这个协程只能由GetThis()方法调用,所以定义成私有方法
     */
    Fiber();
public:
    /**
     * @brief 构造函数,用于创建用户协程
     * @param[in] cb 协程入口函数
     * @param[in] stacksize 栈大小
     * @param[in] run_in_scheduler 本协程是否参与调度器调度,默认为true
     */
    Fiber(std::function<void()> cb, size_t stacksize = 0, bool run_in_scheduler = true);

    /**
     * @brief 析构函数
     */
    ~Fiber();
    /**
     * @brief 重置协程状态和入口函数,复用栈空间,不重新创建栈
     * @param[] cb 
     */
    void reset(std::function<void()> cb);
    /**
     * @brief 将当前协程切到到执行状态
     * @details 当前协程和正在运行的协程进行交换,前者状态变为RUNNING,后者状态变为READY
     */
    void resume();
    /**
     * @brief 当前协程让出执行权
     * @details 当前协程与上次resume时退到后台的协程进行交换,前者状态变为READY,后者状态变为RUNNING
     */
    void yield();
    /**
     * @brief 获取协程ID
     */
    uint64_t getId() const { return m_id; }
    /**
     * @brief 获取协程状态
     */
    State getState() const { return m_state; }
public:
    /**
     * @brief 设置当前正在运行的协程,即设置线程局部变量t_fiber的值
     */
    static void SetThis(Fiber *f);
    /**
     * @brief 返回当前线程正在执行的协程
     * @details 如果当前线程还未创建协程,则创建线程的第一个协程,
     * 且该协程为当前线程的主协程,其他协程都通过这个协程来调度,也就是说,其他协程
     * 结束时,都要切回到主协程,由主协程重新选择新的协程进行resume
     * @attention 线程如果要创建协程,那么应该首先执行一下Fiber::GetThis()操作,以初始化主函数协程
     */
    static Fiber::ptr GetThis();
    /**
     * @brief 获取总协程数
     */
    static uint64_t TotalFibers();
    /**
     * @brief 协程入口函数
     */
    static void MainFunc();
    /**
     * @brief 获取当前协程id
     */
    static uint64_t GetFiberId();
private:
    /// 协程id
    uint64_t m_id        = 0;
    /// 协程栈大小
    uint32_t m_stacksize = 0;
    /// 协程状态
    State m_state        = READY;
    /// 协程上下文
    ucontext_t m_ctx;
    /// 协程栈地址
    void *m_stack = nullptr;
    /// 协程入口函数
    std::function<void()> m_cb;
    /// 本协程是否参与调度器调度,相当于当前协程,是任务协程。
    bool m_runInScheduler;
};

static thread_local 变量

// 全局静态变量,用于生成协程id
static std::atomic<uint64_t> s_fiber_id{0};
// 全局静态变量,用于统计当前的协程数
static std::atomic<uint64_t> s_fiber_count{0};

// 当前正在运行的协程,会不停修改的~
static thread_local Fiber* t_fiber = nullptr;
// 线程局部变量,当前线程的主协程,切换到这个协程,就相当于切换到了主线程中运行,智能指针形式
static thread_local Fiber::ptr t_thread_fiber = nullptr;

关键点 线程如果要创建协程,那么应该首先执行一下Fiber::GetThis()操作,以初始化主函数协程

优化点

  • 参考 boost::context 汇编实现上下文切换,比 ucontext 更高效。⭐⭐⭐
  • 尝试添加共享栈