声明该协程/协程调度/IO协程调度 框架源码均是出自sylar库如有转发注意声明
Fiber
注意事项在看本文章之前读者应该需要了解的知识点: c++11、linux/uinx操作系统下的ucontext函数族、协程的基本原理、以及什么是对称协程和非对称协程
fiber(协程)类的定义
// 协程数量
static std::atomic<uint64_t> t_fiberCount{0};
// 协程ID
static std::atomic<uint64_t> t_fiberId{0};
// 当前线程正在运行的协程
static thread_local Fiber *t_fiber = nullptr;
// 当前线程的主协程
static thread_local Fiber::ptr t_threadFiber = nullptr;
// config定义协程默认协程大小(读者可以使用自己实现的config或者define)sylar库使用yaml实现的配置文件(默认申请1mb)
static auto g_stack_size = Config::Lookup<uint64_t>("fiber.stack_size", 1024 * 1024, "fiber stack size");
// log日志打印输出(这里为了方便就暂时使用标准输出即可读者也可使用自自己实现的log)
static Logger::ptr g_logger = SYLAR_ROOT_LOGGER("")
// 以下是协程类具体的封装和实现
class Fiber : public std::enable_shared_from_this<Fiber>{
public:
typedef std::shared_ptr<Fiber> ptr;
// 定义协程状态
enum State{
INIT, // 初始化状态
EXEC, // 运行状态
HOLD, // yield状态hold
READY, // yield状态ready
TERM, // 结束状态
EXCEPT // 异常状态(个人感觉没啥用)
};
// 用于创建子协程
Fiber(std::function<void()> cb, size_t stack_size = 0);
// 协程占用CPU开始执行(EXEC)
void swapIn();
// 协程让出CPU进入yield状态
void swapOut();
// 获取协程状态
State getState() const { return m_state; }
// 重置协程
void reset(std::function<void()> cb);
// 回收系统资源
~Fiber();
public:
// 获取当前线程正在运行的协程
static Fiber::ptr GetThis();
// 设置当前线程正在运行的协程
static void SetThis(Fiber *fiber);
// 让当前正在运行协程yield并设置状态为hold
static void YieldToHold();
// 让当前正在运行协程yield并设置状态为ready
static void YieldToReady();
// 获取协程数量
static uint64_t GetFiberTotalCount();
// 获取当前正在执行的协程ID
static uint64_t GetFiberID();
private:
/*
用于创建主协程的注意这点很重要因为实现的是非对称的协程所以主协程在当前线程中
主协程只能有一个并且一定要有一个协程做为子协程
*/
Fiber();
// 协程入口函数
static void MainFunc();
private:
// 定义一些协程的私有数据
// 使用posix c提供的上下文实现协程
ucontext_t m_ctx;
// 栈空间大小
size_t m_stack_size = 0;
// 栈空间(什么类型都可以个人习惯使用u_char *)
u_char *m_stack = nullptr;
// 协程运行状态
State m_state = INIT;
// 协程ID
uint64_t m_fiber_id = 0;
// 需要执行任务的回调函数
std::function<void()> m_cb;
};
fiber(协程)类的具体实现
/*
fiber实现之前sylar库封装了那些东西
SYALR_ASSERT2(x, s)、SYLAR_ASSERT(x)这两个宏定义读者可以简单的了解为assert即可
其底层实现实现不过是打印对应协程出错时的栈信息(backtrace函数族+assert)
*/
// 具体实现之前先统一一下内存申请方式(具体的可以视情况而定)sylar库中使用malloc当然malloc已经非常优秀了自己封装的内存池肯定没有人家写的好至于内存泄漏啥的你认为自己写的内存分配器就不会?最主要的就连linux系统都在用malloc。。。当然也可以使用优秀的开源项目比如tcmalloc或者jemalloc(本人只读过tcmalloc部分代码,重点理解tcmalloc的实现原理)这里之所以封装是因为在linux/unix操作系统还能使用mmap进行内存申请
class MallocStackAlloc{
public:
static void *Alloc(size_t size)
{
return malloc(size);
}
static void Dealloc(void *ptr, size_t size)
{
return free(ptr);
}
};
using StackAlloc = MallocStackAlloc;
// 用于创建子协程
Fiber::Fiber(std::function<void()> cb, size_t stack_size)
: m_cb(cb),
m_fiber_id(++t_fiberId)
{
// 栈空间大小
m_stack_size = stack_size ? stack_size : g_stack_size;
// 获取当前上下文信息
if (getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontex");
}
m_stack = static<u_char *>(StackAlloc::Alloc(m_stack_size));
m_ctx.uc_link = nullptr;
m_ctx.uc_stack.ss_size = m_stack_size;
m_ctx.uc_stack.ss_sp = m_stack;
// 上下文绑定入口函数
makecontext(&m_ctx, &Fiber::MainFunc, 0);
++t_fiberCount;
}
/*
用于创建主协程的注意这点很重要因为实现的是非对称的协程所以主协程在当前线程中
主协程只能有一个并且一定要有一个协程做为子协程
*/
Fiber::Fiber()
: m_fiber_id(++t_fiberId)
{
m_state = EXCE;
// 设置当前正在运行的协程
SetThis(this);
// 获取当前上下文信息
if (getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontex");
}
++t_fiberCount;
}
// 协程占用CPU开始执行(EXEC)
void Fiber::swapIn()
{
SYLAR_ASSERT(m_state != EXEC);
SetThis(this);
m_state = EXEC;
if (swapcontext(&t_threadFiber->m_ctx, &m_ctx))
{
SYALR_ASSERT2(false, "swapcontext");
}
}
// 协程让出CPU进入yield状态
void Fiber::swapOut()
{
SetThis(t_threadFiber.get());
if (swapcontext(&m_ctx, &t_threadFiber->m_ctx))
{
SYALR_ASSERT2(false, "swapcontext");
}
}
// 重置协程
void Fiber::reset(std::function<void()> cb)
{
SYLAR_ASSERT(m_state == INIT
|| m_state == TERM
|| m_state == EXCEPT);
m_state = INIT;
m_cb.swap(cb);
if (getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
m_ctx.uc_link = nullptr;
m_ctx.uc_stack.ss_size = m_stack_size;
m_ctx.uc_stack.ss_sp = m_stack;
makecontext(&m_ctx, &Fiber::MainFunc, 0);
}
// 获取当前线程正在运行的协程
Fiber::ptr Fiber::GetThis()
{
if (!t_fiber)
{
t_threadFiber.reset(new Fiber);
SYLAR_ASSERT(t_threadFiber.get() == t_fiber);
}
return t_fiber->shared_from_this();
}
// 设置当前线程正在运行的协程
void Fiber::SetThis(Fiber *fiber)
{
t_fiber = fiber;
}
// 让当前正在运行协程yield并设置状态为hold
void Fiber::YieldToHold()
{
Fiber::ptr fiber = GetFiber();
fiber->m_state = HOLD;
fiber->swapOut();
}
// 让当前正在运行协程yield并设置状态为ready
void Fiber::YieldToReady()
{
Fiber::ptr fiber = GetFiber();
fiber->m_state = READY;
fiber->swapOut();
}
// 获取协程数量
uint64_t Fiber::GetFiberTotalCount()
{
return t_fiberCount;
}
// 获取当前正在执行的协程ID
uint64_t Fiber::GetFiberID()
{
Fiber::ptr fiber = GetThis();
return fiber->m_fiber_id;
}
// 协程入口函数
void Fiber::MainFunc()
{
Fiber::ptr fiber = GetThis();
try
{
fiber->cb();
fiber->cb = nullptr;
fiber->m_state = TERM;
}
catch(std::exception &e)
{
SYLAR_LOG_ERROR(g_logger) << "Fiber Exception: " << e.what();
fiber->m_state = EXCEPT;
}
catch(...)
{
SYLAR_LOG_ERROR(g_logger) << "Fiber Exception!";
fiber->m_state = EXCEPT;
}
Fiber *ptr = fiber.get();
fiber.reset();
ptr.swapOut();
}
// 释放资源
Fiber::~Fiber()
{
if (m_stack)
{
// 有栈空间表示为子协程
SYLAR_ASSERT(m_state == INIT
|| m_state == TERM
|| m_state == EXCEPT);
StackAlloc::Dealloc(m_stack, m_size);
}
else
{
SYALR_ASSERT(!m_cb);
SYALR_ASSERT(m_state == EXEC);
t_threadFiber = nullptr; // 主协程一定要指向空
// 当前运行的协程是不是主协程
Fiber::ptr fiber = GetFiber();
if (*fiber == this)
{
SetThis(nullptr);
}
}
--t_fiberCount;
}