服务器学习日记-协程库实现
协程介绍
基础
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
主要以分享服务器相关知识为主

