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;
};