协程介绍
基础
1.协程可以理解成用户态的轻量级线程,切换由用户操作。
2.协程切换很快,不会陷入内核态。
3.协程拥有自己的寄存器上下文和栈, 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈。
优点
1.协程具有极高的执行效率 因为子程序切换不是线程切换,是由程序自身控制,因此协程没有线程切换的开销, 多线程的线程数量越多,协程的性能优势就越明显。
2.访问共享资源不需要多线程的锁机制, 因为只有一个线程, 也不存在同时写变量冲突, 所以在协程中控制共享资源无需加锁, 只需要判断状态就好了,执行效率比多线程高很多, 而且代码编写难度也可以相应降低。
3.以同步代码的方式写异步逻辑。
缺点
无法利用多核资源, 除非和多进程配合
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保存上下文的特定机器, 包括调用线程的特定寄存器等等
简而言之这个数据结构是用来保存上下文的
相关函数
int getcontext(context_t * ucp)
这个函数是用来获取当前上下文的,将上下文保存到ucp中去。
成功返回0; 失败返回-1, 并设置errno(表明为系统函数)
void makecontext(ucontext_t ucp, void(func)(), int argc, ...);
1.创建一个目标上下文 创建方式: (1) getcontext, (2) 指定分配给上下文的栈uc_stack.ss_sp, (3) 指定这块栈的大小uc_stack.ss_size, (4) 指定uc_stack.ss_flags(可选) (5) 指定后继上下文uc_link(可选)。
2.makecontext可以修改通过getcontext初始化得到的上下文, (必须先调用getcontext), 然后为ucp指定一个栈空间ucp->stack, 设置后继的上下文ucp->uc_link(uc_link不一定使用,但是用户协程调用完成之后要使用swapcontext切换回主协程,比较麻烦)。
3.当上下文通过setcontext或者swapcontext激活后, 执行func函数(argc为后续的参数个数, 可变参数). 当func执行返回后, 继承的上下文被激活(ucp->uc_link), 如果为NULL, 则线程退出
int setcontext(const ucontext_t *ucp)
1.设置当前的上下文为ucp(激活ucp)。
2.ucp来自getcontext, 那么上下文恢复至ucp
3.ucp来自makecontext, 那么将会调用makecontext函数的第二个参数指向的函数func, 如果func返回, 则恢复至ucp->uc_link指定的后继上下文, 如果该ucp中的uc_link为NULL, 那么线程退出
4.成功不返回, 失败返回-1, 设置errno
int swapcontext(ucontext_t *oucp, ucontext_t *ucp)
由oucp上下文切换到ucp上下文。
实现
封装assert
利用execinfo.h中的backtrace与backtrace_symbols获取栈的调用上下文。
void Backtrace(std::vector<std::string>& bt, int size, int skip) { void** array = (void**)malloc((sizeof(void*) * size)); size_t s = ::backtrace(array, size); char** strings = backtrace_symbols(array, s); if(strings == NULL) { SYLAR_LOG_ERROR(g_logger) << "backtrace_synbols error"; return; } for(size_t i = skip; i < s; ++i) { bt.push_back(strings[i]); } free(strings); free(array); } std::string BacktraceToString(int size, int skip, const std::string& prefix) { std::vector<std::string> bt; Backtrace(bt, size, skip); std::stringstream ss; for(size_t i = 0; i < bt.size(); ++i) { ss << prefix << bt[i] << std::endl; } return ss.str(); }
封装宏使得调用方便
#if defined __GNUC__ || defined __llvm__ /// LIKCLY 宏的封装, 告诉编译器优化,条件大概率成立 # define SYLAR_LIKELY(x) __builtin_expect(!!(x), 1) /// LIKCLY 宏的封装, 告诉编译器优化,条件大概率不成立 # define SYLAR_UNLIKELY(x) __builtin_expect(!!(x), 0) #else # define SYLAR_LIKELY(x) (x) # define SYLAR_UNLIKELY(x) (x) #endif /// 断言宏封装 #define SYLAR_ASSERT(x) \ if(SYLAR_UNLIKELY(!(x))) { \ SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ASSERTION: " #x \ << "\nbacktrace:\n" \ << sylar::BacktraceToString(100, 2, " "); \ assert(x); \ } /// 断言宏封装 #define SYLAR_ASSERT2(x, w) \ if(SYLAR_UNLIKELY(!(x))) { \ SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ASSERTION: " #x \ << "\n" << w \ << "\nbacktrace:\n" \ << sylar::BacktraceToString(100, 2, " "); \ assert(x); \ }
ucontext封装
#ifndef __SYLAR_FIBER_H__ #define __SYLAR_FIBER_H__ #include <memory> #include <functional> #include <ucontext.h> namespace sylar { class Scheduler; /** * @brief 协程类 */ class Fiber : public std::enable_shared_from_this<Fiber> { friend class Scheduler; public: typedef std::shared_ptr<Fiber> ptr; /** * @brief 协程状态 */ enum State { /// 初始化状态 INIT, /// 暂停状态 HOLD, /// 执行中状态 EXEC, /// 结束状态 TERM, /// 可执行状态 READY, /// 异常状态 EXCEPT }; private: /** * @brief 无参构造函数 * @attention 每个线程第一个协程的构造 */ Fiber(); public: /** * @brief 构造函数 * @param[in] cb 协程执行的函数 * @param[in] stacksize 协程栈大小 * @param[in] use_caller 是否在MainFiber上调度 */ Fiber(std::function<void()> cb, size_t stacksize = 0, bool use_caller = false); /** * @brief 析构函数 */ ~Fiber(); /** * @brief 重置协程执行函数,并设置状态 * @pre getState() 为 INIT, TERM, EXCEPT * @post getState() = INIT */ void reset(std::function<void()> cb); /** * @brief 将当前协程切换到运行状态 * @pre getState() != EXEC * @post getState() = EXEC */ void swapIn(); /** * @brief 将当前协程切换到后台 */ void swapOut(); /** * @brief 将当前线程切换到执行状态 * @pre 执行的为当前线程的主协程 */ void call(); /** * @brief 将当前线程切换到后台 * @pre 执行的为该协程 * @post 返回到线程的主协程 */ void back(); /** * @brief 返回协程id */ uint64_t getId() const { return m_id;} /** * @brief 返回协程状态 */ State getState() const { return m_state;} public: /** * @brief 设置当前线程的运行协程 * @param[in] f 运行协程 */ static void SetThis(Fiber* f); /** * @brief 返回当前所在的协程 */ static Fiber::ptr GetThis(); /** * @brief 将当前协程切换到后台,并设置为READY状态 * @post getState() = READY */ static void YieldToReady(); /** * @brief 将当前协程切换到后台,并设置为HOLD状态 * @post getState() = HOLD */ static void YieldToHold(); /** * @brief 返回当前协程的总数量 */ static uint64_t TotalFibers(); /** * @brief 协程执行函数 * @post 执行完成返回到线程主协程 */ static void MainFunc(); /** * @brief 协程执行函数 * @post 执行完成返回到线程调度协程 */ static void CallerMainFunc(); /** * @brief 获取当前协程的id */ static uint64_t GetFiberId(); private: /// 协程id uint64_t m_id = 0; /// 协程运行栈大小 uint32_t m_stacksize = 0; /// 协程状态 State m_state = INIT; /// 协程上下文 ucontext_t m_ctx; /// 协程运行栈指针 void* m_stack = nullptr; /// 协程运行函数 std::function<void()> m_cb; }; } #endif