目录

sylar-webserver4-协程模块

【sylar-webserver】4 协程模块

知识点

1 Fiber

有栈协程

  typedef struct ucontext_t
  {
    unsigned long int __ctx(uc_flags);
    struct ucontext_t *uc_link;					// uc_link 指向后继上下文
    stack_t uc_stack;							// uc_stack 为该上下文中使用的栈;
    mcontext_t uc_mcontext;						// uc_mcontext 这个结构体依赖于机器且不透明,作用是保存硬件上下文,包括硬件寄存器的值 。
    sigset_t uc_sigmask;						// uc_sigmask 为该上下文中的阻塞信号集合
    struct _libc_fpstate __fpregs_mem;
  } ucontext_t;

 typedef struct 
 {
     void  *ss_sp;     /* Base address of stack */		// 协程的栈空间的起始地址,可以是用户级的栈变量指针,也可以是堆变量指针。
     int    ss_flags;  /* Flags */						// flag,在协程使用中设置该值为0。
     size_t ss_size;   /* Number of bytes in stack */	// 表示栈空间的大小。
 } stack_t;
// 创建Fiber
#include <ucontext.h>

ucontext_t m_ctx;

// 获取当前上下文,初始化上下文 ucontext_t。
// 当 getcontext 之后,如果不指定 uc_link, uc_stack, makecontext。 那么相当于初始化了 一个 主协程 m_ctx
getcontext(&m_ctx);

m_ctx.uc_link = nullptr;				// 下一个指向的上下文 任务
m_ctx.uc_stack.ss_sp = m_stack;			// 指定分配的 堆空间,用于协程函数堆栈
m_ctx.uc_stack.ss_size = m_stacksize;

// void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
// 函数将ucp 对应的上下文中的指令地址指向 func函数(协程)地址,argc表示func的入参个数,如果入参为空,该值设置为0。如果argc有值,入参为一系列 (int)整型的数据。
makecontext(&m_ctx, &Fiber::MainFunc, 0);	

// int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
// 保存当前协程的上下文到oucp,然后激活(切换到) ucp所指向的上下文对应的协程。返回值:当调用成功后,swapcontext()不会返回;当调用失败后,返回-1并设置合适的errno。


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

// 从主协程切换到 m_ctx目标任务协程
swapcontext(&(t_thread_fiber->m_ctx), &m_ctx);

// m_ctx任务协程 切换回 主协程
swapcontext(&m_ctx, &(t_thread_fiber->m_ctx));

设计思路

类的设计,当前还不涉及 调度,所以只是 主协程和任务协程 的相互切换

// 全局静态变量,用于生成协程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;
/**
 * @brief 协程类
 * 
 * 主协程:GetThis() -> Fiber()
 * 
 * 任务协程:
 * Fiber(std::function<void()> cb, size_t stacksize = 0, bool run_in_scheduler = true);
 *  
 * task_fiber->resume();
 *  
 * task_fiber->yield();
 */
class Fiber : public std::enable_shared_from_this<Fiber> {
public:
    typedef std::shared_ptr<Fiber> ptr;
    /**
     * @brief 协程状态
     * @details 在sylar基础上进行了状态简化,只定义三态转换关系,也就是协程要么正在运行(RUNNING),
     * 要么准备运行(READY),要么运行结束(TERM)。不区分协程的初始状态,初始即READY。不区分协程是异常结束还是正常结束,
     * 只要结束就是TERM状态。也不区别HOLD状态,协程只要未结束也非运行态,那就是READY状态。
     */
    enum State {
        /// 就绪态,刚创建或者yield之后的状态
        READY,
        /// 运行态,resume之后的状态
        RUNNING,
        /// 结束态,协程的回调函数执行完之后为TERM状态
        TERM
    };
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
     * 在这里设置SetThis(this); 交换前确定好 t_fiber 当前执行的协程Fiber*
     */
    void resume();
    /**
     * @brief 当前协程让出执行权
     * @details 当前协程与上次resume时退到后台的协程进行交换,前者状态变为READY,后者状态变为RUNNING
     * 在这里设置SetThis(t_thread_fiber.get()); 交换前确定好 t_fiber 当前执行的协程Fiber*
     */
    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 协程入口函数
     * 所有的任务协程 指向这个这里,在这里面执行对应的 m_cb
     */
    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;
};