知识点
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 更高效。⭐⭐⭐
- 尝试添加共享栈