什么是协程
fiber.h
#ifndef __SYLAR_FIBER_H__
#define __SYLAR_FIBER_H__
#include <memory>
#include <ucontext.h>
#include "thread.h"
namespace sylar{
class Sheduler;
class Fiber : public std::enable_shared_from_this<Fiber>{
friend class Scheduler; // 声明友元类
public:
typedef std::shared_ptr<Fiber> ptr;
enum State{ // 协程的状态
INIT, // 初始状态,未开始执行
HOLD, // 协程被挂起,等待被恢复
EXEC, // 协程正在执行
TERM, // 协程已经执行完成
READY, // 协程准备好执行
EXCEPT // 协程遇到异常或错误
};
private:
Fiber();
public:
Fiber(std::function<void()> cb, size_t stacksize = 0, bool use_caller = false);
~Fiber();
// 重置协程函数,并重置状态
void reset(std::function<void()> cb);
// 切换到当前协程执行
void swapIn();
// 切换到后台执行
void swapOut();
void call();
void back();
uint64_t getId() const {return m_id;}
State getState() const {return m_state;}
public:
// 设置当前协程
static void SetThis(Fiber* f);
// 返回当前协程
static Fiber::ptr GetThis();
// 协程切换到后台,并且设置为Ready状态
static void YieldToReady();
// 协程切换到后台,并且设置为Hold状态
static void YieldToHold();
// 总协程数
static uint64_t TotalFibers();
static void MainFunc();
static void CallerMainFunc();
// 返回当前协程的 id
static uint64_t GetFiberId();
private:
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
-
将默认构造函数设置为私有,通常是为了防止用户直接通过默认构造函数创建
Fiber对象。将默认构造函数设为private可以确保所有的Fiber实例只能通过特定的接口创建,比如使用提供的参数化构造函数Fiber(std::function<void()> cb, size_t stacksize = 0, bool use_caller = false)。这样可以保证每个Fiber都有定义的行为(比如绑定了一个有效的回调函数cb)。只有
Fiber类内部或者friend类(如Scheduler)才可以调用此构造函数。
Fiber 类 变量与函数解析
(一)五个静态变量
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_threadFiber = nullptr;
static ConfigVar<uint32_t>::ptr g_fiber_stack_size =
Config::Lookup<uint32_t>("fiber.stack_size", 1024 * 1024, "fiber stack size");
-
static std::atomic<uint64_t> s_fiber_id {0};- 原子变量,用于生成和跟踪协程的唯一 ID。
std::atomic确保在多线程环境下进行线程安全的递增或修改操作。每创建一个新协程,s_fiber_id就会递增,为协程分配唯一标识符。
- 原子变量,用于生成和跟踪协程的唯一 ID。
-
static std::atomic<uint64_t> s_fiber_count {0};- 原子变量,用于跟踪当前活跃协程的数量。每创建一个新协程时,
s_fiber_count会增加,当协程结束时会减少。这样可以提供有关总协程数量的实时统计信息。
- 原子变量,用于跟踪当前活跃协程的数量。每创建一个新协程时,
-
两个变量使用列表初始化方式。列表初始化能够避免窄化转换,即不允许从较大的数据类型隐式转换为较小的数据类型,从而防止潜在的数据丢失风险。
-
static thread_local Fiber* t_fiber = nullptr;- 线程局部变量,表示当前正在执行的协程指针。
thread_local修饰符确保每个线程都有自己的t_fiber实例。这样可以使每个线程独立管理自己的当前协程状态,不会相互干扰。
- 线程局部变量,表示当前正在执行的协程指针。
-
static thread_local Fiber::ptr t_threadFiber = nullptr;- 线程局部变量,用于持有主协程(即线程的初始协程)的智能指针。它的作用是确保主协程在其所在的线程生命周期内不会被销毁。这样可以防止在协程调度中主协程被意外回收或销毁,从而保证协程系统的稳定性。
-
g_fiber_stack_size,它是ConfigVar<uint32_t>类型的智能指针,用于读取和管理协程栈大小的配置项。如果配置文件或配置源中已有fiber.stack_size这一项,则使用其值。如果没有定义该配置项,则使用默认值1024 * 1024(1 MB)。
(二)class MallocStackAllocator
class MallocStackAllocator{
public:
static void* Alloc(size_t size)
{
return malloc(size);
}
static void Dealloc(void* vp, size_t size)
{
return free(vp);
}
};
这个类封装了 C 标准库的 malloc 和 free 函数,用于分配和释放内存块。在这里负责分配和释放协程栈的内存。
(三)私有默认构造函数
// 线程局部变量,表示当前正在执行的协程指针。
static thread_local Fiber* t_fiber = nullptr;
...
void Fiber::SetThis(Fiber *f)
{
t_fiber = f;
}
...
Fiber::Fiber()
{
m_state = EXEC;
SetThis(this);
// 获取协程的上下文,并将其保存在 m_ctx 中
if(getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
++s_fiber_count; // 当前活跃的协程数量
SYLAR_LOG_DEBUG(g_logger) << "Fiber::Fiber";
}
这个构造函数的主要职责是:初始化协程的状态、获取当前协程的上下文,为协程切换做准备。此构造函数在 GetThis() 函数中使用了。
(四)GetThis()
// 返回当前协程
Fiber::ptr Fiber::GetThis()
{
if(t_fiber)
{
return t_fiber->shared_from_this();
}
Fiber::ptr main_fiber(new Fiber);
SYLAR_ASSERT(t_fiber == main_fiber.get());
t_threadFiber = main_fiber;
return t_fiber->shared_from_this();
}
-
如果
t_fiber存在,直接返回其shared_ptr,表示当前线程已经有一个Fiber正在执行。 -
当
t_fiber为nullptr,说明当前线程没有一个关联的协程,于是创建一个新的主协程对象main_fiber。 -
将
main_fiber的shared_ptr存储到t_threadFiber中,确保当前线程持有主协程的引用,从而避免它被销毁。
(五)构造函数
Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller) :
m_id(++s_fiber_id), m_cb(cb)
{
++s_fiber_count;
// 设置为传入的 stacksize 或默认使用 g_fiber_stack_size 中配置的值
m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();
// 为协程分配栈空间
m_stack = StackAllocator::Alloc(m_stacksize);
// 获取当前上下文并保存到 m_ctx
if(getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
m_ctx.uc_link = nullptr; // uc_link指向执行完入口函数返回的位置,为nullptr则退出
m_ctx.uc_stack.ss_sp = m_stack; // 设置协程栈
m_ctx.uc_stack.ss_size = m_stacksize; // 设置栈的大小
if(!use_caller)
{
makecontext(&m_ctx, &Fiber::MainFunc, 0); // 将上下文绑定入口函数
}
else
{
makecontext(&m_ctx, &Fiber::CallerMainFunc, 0); // 上下文绑定入口函数
}
SYLAR_LOG_DEBUG(g_logger) << "Fiber::Fiber id=" << m_id;
}
(六)析构函数
Fiber::~Fiber()
{
--s_fiber_count; // 减少全局协程计数
if(m_stack) // 分配了栈空间
{
// 确保线程的状态是终止、初始化或者异常状态
// 这些状态表示协程应该已经 退出 或处于 未执行 的状态
SYLAR_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
StackAllocator::Dealloc(m_stack, m_stacksize); // 释放栈空间
}
else // 未分配栈空间,即为主协程
{
SYLAR_ASSERT(!m_cb); // 主协程没有设置回调函数
SYLAR_ASSERT(m_state == EXEC); // 主协程一直处于 EXEC 状态
Fiber* cur = t_fiber;
if(cur == this) // 当前协程是正在执行的协程
{
SetThis(nullptr); // 将 t_fiber 置为 nullptr
}
}
SYLAR_LOG_DEBUG(g_logger) << "Fiber::~Fiber id=" << m_id;
}
- 主协程与其他协程的区别在于它通常并不使用独立的栈,主协程的栈是线程的默认栈(即主线程栈)。
- 主协程是程序启动时自动创建的协程,通常它代表的是执行程序的主体(即线程的起点)。在许多协程库中,主协程通常不需要显式地定义一个回调函数,因为它是程序启动时最先执行的代码。
(七)reset()
重置协程的状态,重复利用已结束的协程,复用其栈空间,创建新协程
// 重置协程函数并重置状态
void Fiber::reset(std::function<void()> cb)
{
SYLAR_ASSERT(m_stack); // 确保协程有分配的栈空间
// 确保当前状态适合重置
SYLAR_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
m_cb = cb;
if(getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
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); // 设置协程的入口函数
m_state = INIT; // 设置协程的状态
}
- 首先保证协程必须已经分配了栈空间。如果没有栈空间,协程无法执行。栈空间对于协程的正常运行至关重要,因为协程的栈是在协程创建时分配的,用于保存局部变量、函数调用信息等。
(八)swapIn()
// 切换到当前协程执行
void Fiber::swapIn()
{
SetThis(this); // t_fiber = this,即将当前协程赋值给正在执行的协程 t_fiber
SYLAR_ASSERT(m_state != EXEC); // 确保当前协程不在 EXEC 状态
m_state = EXEC; // 将协程状态设置为 EXEC
// 使用 swapcontext 切换上下文,从线程的主协程上下文切换到当前协程的上下文
if(swapcontext(&Scheduler::GetMainFiber()->m_ctx, &m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
}
SYLAR_ASSERT(m_state != EXEC);确保当前协程m_state不是EXEC(执行状态)。如果当前协程正在执行(即m_state == EXEC),则不应该再次切换到该协程,因此加上断言是为了保证切换逻辑的正确性。Scheduler::GetMainFiber()是调度器的主协程,它通常是一个特殊的协程,用于管理和调度其他协程。
(九)swapOut()
将当前协程的执行上下文保存,并切换回主协程(通常是调度器中的主协程)。它是协程切换机制中的一部分,通常在协程需要挂起(暂停)并让调度器或主协程恢复时使用。
// 切换到后台执行
void Fiber::swapOut()
{
SetThis(Scheduler::GetMainFiber());
if(swapcontext(&m_ctx, &Scheduler::GetMainFiber()->m_ctx))
{
SYLAR_ASSERT2(false, "swapcontext");
}
}
swapcontext的作用是保存当前协程的上下文(m_ctx)并恢复目标协程的上下文(Scheduler::GetMainFiber()->m_ctx)。swapcontext函数会触发上下文切换,保存当前协程的状态并恢复主协程的状态。恢复后,控制权交还给主协程,而当前协程(即调用swapOut()的协程)会被暂停,直到下一次被恢复。
(十)call()
此函数用于启动协程的执行,将主协程的上下文切换到目标协程。用于在协程调度中激活某个特定的协程。
void Fiber::call()
{
// t_fiber = this,即将当前协程赋值给正在执行的协程 t_fiber
SetThis(this);
m_state = EXEC;
// SYLAR_ASSERT(GetThis() == t_threadFiber);
SYLAR_LOG_ERROR(g_logger) << getId();
if(swapcontext(&t_threadFiber->m_ctx, &m_ctx))
{
SYLAR_ASSERT2(false, "swapcontext");
}
}
(十一)back()
将当前协程挂起并将控制权交回主协程,这是协程调度的核心操作之一,能够实现协程之间的协作式多任务处理。
void Fiber::back()
{
SetThis(t_threadFiber.get()); // 将当前协程设置为主协程
if(swapcontext(&m_ctx, &t_threadFiber->m_ctx))
{
SYLAR_ASSERT2(false, "swapcontext");
}
}
(十二)YieldToReady() 与 YieldToHold()
// 协程切换到后台,并设置为 Ready 状态
void Fiber::YieldToReady()
{
Fiber::ptr cur = GetThis(); // 获取当前协程的指针
cur->m_state = READY; // 将当前协程的状态设为 READY
cur->swapOut(); // 切换当前协程的上下文,交出控制权
}
// 协程切换到后台,并设置为 Hold 状态
void Fiber::YieldToHold()
{
Fiber::ptr cur = GetThis(); // 获取当前协程的指针
cur->m_state = HOLD; // 将当前协程的状态设为 HOLD
cur->swapOut(); // 切换当前协程的上下文,交出控制权
}
(十三)MainFunc 与 CallerMainFunc()
void Fiber::MainFunc()
{
// 获取当前正在执行的协程对象,并将其存储在智能指针 cur 中。
Fiber::ptr cur = GetThis();
// 确保当前协程对象有效。如果 cur 为 nullptr,则程序会中止并输出错误信息。
SYLAR_ASSERT(cur);
try {
// 执行协程的回调函数 m_cb,这是协程的核心任务。
cur->m_cb();
// 回调函数执行完毕后,将 m_cb 清空以释放回调函数所占用的资源。
cur->m_cb = nullptr;
// 将协程的状态设置为 TERM,表示协程已经正常终止。
cur->m_state = TERM;
}
catch (const std::exception& e) {
// 如果捕获到标准异常,将协程状态设置为 EXCEPT,表示协程遇到了异常情况。
cur->m_state = EXCEPT;
// 记录异常信息到日志,包括异常描述和协程 ID,以及调用栈信息。
SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << e.what()
<< " fiber_id=" << cur->getId()
<< std::endl
<< sylar::BacktraceToString();
}
catch (...) {
// 捕获所有其他类型的异常,将协程状态设置为 EXCEPT。
cur->m_state = EXCEPT;
// 记录未知异常的日志信息。
SYLAR_LOG_ERROR(g_logger) << "Fiber Except";
}
// 获取当前协程智能指针的裸指针以保留对协程对象的引用。
auto raw_ptr = cur.get();
// 将智能指针 cur 置空,释放对协程对象的控制权,使其在不再引用时被销毁。
cur.reset();
// 切换上下文,执行 swapOut() 将当前协程切换回主协程。
raw_ptr->swapOut();
// 由于 swapOut() 结束后,协程的控制权不应该再回到 MainFunc()。如果出现这种情况,
// 程序会中止,并输出错误信息和协程 ID。这是防御性检查,确保逻辑正确。
SYLAR_ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));
}
void Fiber::CallerMainFunc()
{
Fiber::ptr cur = GetThis(); // 获取当前正在执行的协程
SYLAR_ASSERT(cur); // 确保当前协程有效
try
{
cur->m_cb(); // 执行当前协程的回调函数
cur->m_cb = nullptr; // 执行完毕后,将回调函数置为 nullptr,防止多次调用
cur->m_state = TERM; // 将协程的状态设置为终止状态
}
catch(const std::exception& e)
{
cur->m_state = EXCEPT; // 如果发生异常,将协程状态设置为异常状态
SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << e.what()
<< " fiber_id=" << cur->getId()
<< std::endl
<< sylar::BacktraceToString(); // 记录异常信息和栈追踪
}
catch(...)
{
cur->m_state = EXCEPT; // 捕获所有其他异常
SYLAR_LOG_ERROR(g_logger) << "Fiber Except"; // 记录异常
}
auto raw_ptr = cur.get(); // 获取当前协程的原始指针
cur.reset(); // 释放 shared_ptr 引用,执行析构
raw_ptr->back(); // 将当前协程挂起并将控制权交回主协程
SYLAR_ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId())); // 此行不应被执行,若执行表示有逻辑错误
}
-
其中
BacktraceToString()函数如下:// 获取当前线程的堆栈追踪信息 void Backtrace(std::vector<std::string> &bt, int size, int skip) { // 分配一个存储指针的数组,大小为size void** array= (void**)malloc(sizeof(void*) * size); // 获取当前线程的堆栈信息,存储到 array 中,返回获取的堆栈大小 size_t s = ::backtrace(array, size); // 将堆栈信息转换为符号名称(函数名及其偏移量) char** strings = backtrace_symbols(array, s); if(strings == NULL) // 转换失败 { SYLAR_LOG_ERROR(g_logger) << "backtrace_symbols error"; //free(array); return ; } // 跳过前 skip 个栈帧,存储其余栈帧的信息到 bt 中 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(); } -
在 Fiber 类的构造函数中使用到了这两个函数:
if(!use_caller) // { makecontext(&m_ctx, &Fiber::MainFunc, 0); // 对应 swapOut,切换到Scheduler::GetMainFiber } else { makecontext(&m_ctx, &Fiber::CallerMainFunc, 0); // 对应 back,切换到t_threadFiber }use_caller为false时表示协程为标准协程,协程执行完任务后挂起,切换到Scheduler::GetMainFiber。use_caller为true时表示协程为调度协程,协程执行完任务后挂起,切换到t_threadFiber。
fiber.cc
#include "fiber.h"
#include "macro.h"
#include "config.h"
#include <atomic>
#include "log.h"
#include "scheduler.h"
namespace sylar{
static Logger::ptr g_logger = SYLAR_LOG_NAME("system");
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_threadFiber = nullptr;
static ConfigVar<uint32_t>::ptr g_fiber_stack_size =
Config::Lookup<uint32_t>("fiber.stack_size", 1024 * 1024, "fiber stack size");
class MallocStackAllocator{
public:
static void* Alloc(size_t size)
{
return malloc(size);
}
static void Dealloc(void* vp, size_t size)
{
return free(vp);
}
};
using StackAllocator = MallocStackAllocator;
Fiber::Fiber()
{
m_state = EXEC;
SetThis(this);
if(getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
++s_fiber_count;
SYLAR_LOG_DEBUG(g_logger) << "Fiber::Fiber";
}
Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller) :
m_id(++s_fiber_id), m_cb(cb)
{
++s_fiber_count;
// 设置为传入的 stacksize 或默认使用 g_fiber_stack_size 中配置的值
m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();
// 为协程分配栈空间
m_stack = StackAllocator::Alloc(m_stacksize);
// 获取当前上下文并保存到 m_ctx
if(getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
m_ctx.uc_link = nullptr; // uc_link指向执行完入口函数返回的位置,为nullptr则退出
m_ctx.uc_stack.ss_sp = m_stack; // 设置协程栈
m_ctx.uc_stack.ss_size = m_stacksize; // 设置栈的大小
if(!use_caller)
{
makecontext(&m_ctx, &Fiber::MainFunc, 0); // 将上下文绑定入口函数
}
else
{
makecontext(&m_ctx, &Fiber::CallerMainFunc, 0); // 上下文绑定入口函数
}
SYLAR_LOG_DEBUG(g_logger) << "Fiber::Fiber id=" << m_id;
}
Fiber::~Fiber()
{
--s_fiber_count; // 减少全局协程计数
if(m_stack) // 分配了栈空间
{
// 确保线程的状态是终止、初始化或者异常状态
// 这些状态表示协程应该已经 退出 或处于 未执行 的状态
SYLAR_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
StackAllocator::Dealloc(m_stack, m_stacksize); // 释放栈空间
}
else // 未分配栈空间,即为主协程
{
SYLAR_ASSERT(!m_cb); // 主协程没有设置回调函数
SYLAR_ASSERT(m_state == EXEC); // 主协程一直处于 EXEC 状态
Fiber* cur = t_fiber;
if(cur == this) // 当前协程是正在执行的协程
{
SetThis(nullptr); // 将 t_fiber 置为 nullptr
}
}
SYLAR_LOG_DEBUG(g_logger) << "Fiber::~Fiber id=" << m_id;
}
// 重置协程函数并重置状态
void Fiber::reset(std::function<void()> cb)
{
SYLAR_ASSERT(m_stack); // 确保协程有分配的栈空间
// 确保当前状态适合重置
SYLAR_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
m_cb = cb;
if(getcontext(&m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
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); // 设置协程的入口函数
m_state = INIT; // 设置协程的状态
}
// 切换到当前协程执行
void Fiber::swapIn()
{
SetThis(this); // t_fiber = this,即将当前协程赋值给正在执行的协程 t_fiber
SYLAR_ASSERT(m_state != EXEC); // 确保当前协程不在 EXEC 状态
m_state = EXEC; // 将协程状态设置为 EXEC
// 使用 swapcontext 切换上下文,从线程的主协程上下文切换到当前协程的上下文
if(swapcontext(&Scheduler::GetMainFiber()->m_ctx, &m_ctx))
{
SYLAR_ASSERT2(false, "getcontext");
}
}
// 切换到后台执行
void Fiber::swapOut()
{
SetThis(Scheduler::GetMainFiber());
if(swapcontext(&m_ctx, &Scheduler::GetMainFiber()->m_ctx))
{
SYLAR_ASSERT2(false, "swapcontext");
}
}
void Fiber::call()
{
// t_fiber = this,即将当前协程赋值给正在执行的协程 t_fiber
SetThis(this);
m_state = EXEC;
// SYLAR_ASSERT(GetThis() == t_threadFiber);
SYLAR_LOG_ERROR(g_logger) << getId();
if(swapcontext(&t_threadFiber->m_ctx, &m_ctx))
{
SYLAR_ASSERT2(false, "swapcontext");
}
}
// 将当前协程挂起并将控制权交回主协程
void Fiber::back()
{
SetThis(t_threadFiber.get());
if(swapcontext(&m_ctx, &t_threadFiber->m_ctx))
{
SYLAR_ASSERT2(false, "swapcontext");
}
}
// 设置当前协程
void Fiber::SetThis(Fiber *f)
{
t_fiber = f;
}
// 返回当前协程
Fiber::ptr Fiber::GetThis()
{
if(t_fiber)
{
return t_fiber->shared_from_this();
}
Fiber::ptr main_fiber(new Fiber);
SYLAR_ASSERT(t_fiber == main_fiber.get());
t_threadFiber = main_fiber;
return t_fiber->shared_from_this();
}
// 协程切换到后台,并设置为 Ready 状态
void Fiber::YieldToReady()
{
Fiber::ptr cur = GetThis(); // 获取当前协程的指针
cur->m_state = READY; // 将当前协程的状态设为 READY
cur->swapOut(); // 切换当前协程的上下文,交出控制权
}
// 协程切换到后台,并设置为 Hold 状态
void Fiber::YieldToHold()
{
Fiber::ptr cur = GetThis(); // 获取当前协程的指针
cur->m_state = HOLD; // 将当前协程的状态设为 HOLD
cur->swapOut(); // 切换当前协程的上下文,交出控制权
}
// 总协程数
uint64_t Fiber::TotalFibers()
{
return s_fiber_count;
}
void Fiber::MainFunc()
{
// 获取当前正在执行的协程对象,并将其存储在智能指针 cur 中。
Fiber::ptr cur = GetThis();
// 确保当前协程对象有效。如果 cur 为 nullptr,则程序会中止并输出错误信息。
SYLAR_ASSERT(cur);
try {
// 执行协程的回调函数 m_cb,这是协程的核心任务。
cur->m_cb();
// 回调函数执行完毕后,将 m_cb 清空以释放回调函数所占用的资源。
cur->m_cb = nullptr;
// 将协程的状态设置为 TERM,表示协程已经正常终止。
cur->m_state = TERM;
}
catch (const std::exception& e) {
// 如果捕获到标准异常,将协程状态设置为 EXCEPT,表示协程遇到了异常情况。
cur->m_state = EXCEPT;
// 记录异常信息到日志,包括异常描述和协程 ID,以及调用栈信息。
SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << e.what()
<< " fiber_id=" << cur->getId()
<< std::endl
<< sylar::BacktraceToString();
}
catch (...) {
// 捕获所有其他类型的异常,将协程状态设置为 EXCEPT。
cur->m_state = EXCEPT;
// 记录未知异常的日志信息。
SYLAR_LOG_ERROR(g_logger) << "Fiber Except";
}
// 获取当前协程智能指针的裸指针以保留对协程对象的引用。
auto raw_ptr = cur.get();
// 将智能指针 cur 置空,释放对协程对象的控制权,使其在不再引用时被销毁。
cur.reset();
// 切换上下文,执行 swapOut() 将当前协程切换回主协程。
raw_ptr->swapOut();
// 由于 swapOut() 结束后,协程的控制权不应该再回到 MainFunc()。如果出现这种情况,
// 程序会中止,并输出错误信息和协程 ID。这是防御性检查,确保逻辑正确。
SYLAR_ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));
}
void Fiber::CallerMainFunc()
{
Fiber::ptr cur = GetThis();
SYLAR_ASSERT(cur);
try
{
cur->m_cb();
cur->m_cb = nullptr;
cur->m_state = TERM;
}
catch(const std::exception& e)
{
cur->m_state = EXCEPT;
SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << e.what()
<< " fiber_id=" << cur->getId()
<< std::endl
<< sylar::BacktraceToString();
}
catch(...)
{
cur->m_state = EXCEPT;
SYLAR_LOG_ERROR(g_logger) << "Fiber Except";
}
auto raw_ptr = cur.get();
cur.reset();
raw_ptr->back();
SYLAR_ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));
}
uint64_t Fiber::GetFiberId()
{
if(t_fiber)
{
return t_fiber->getId();
}
return 0;
}
}
知识点
什么是上下文
上下文(Context)在计算机科学中指的是程序执行时所需的所有状态信息。在协程、线程和进程的切换中,保存和恢复上下文是至关重要的步骤。上下文包含了用于保存程序当前运行状态的信息,使得程序在被中断后能够恢复执行而不会丢失状态。
上下文通常包括以下几部分:
- CPU 寄存器:如程序计数器(PC,Program Counter)、栈指针(SP,Stack Pointer)和通用寄存器等。这些寄存器保存当前执行的位置和临时数据。
- 栈信息:包括调用栈、局部变量和返回地址等信息。
- 程序状态字:如状态寄存器中保存的标志位,用于指示程序当前状态(如中断标志、条件标志等)。
- 内存空间:有时,尤其是进程上下文,还包括了整个虚拟内存空间的信息。
- 其他资源:对于进程,可能还包括文件描述符、信号处理信息等。
上下文的应用场景
上下文切换是实现多任务处理的基础。不同场景中对上下文的定义和管理略有不同:
-
协程:
- 协程上下文保存了当前协程的执行状态,包括寄存器内容、栈指针等信息,使得协程可以在挂起后继续从中断点恢复执行。
- 例如,在实现协程库中,调用
getcontext和setcontext等系统调用来保存和恢复上下文。
-
线程:
- 每个线程都有自己的上下文,包含寄存器状态和栈空间。当线程被调度器暂停时,当前线程的上下文会被保存,以便在下次恢复执行时继续运行。
-
进程:
- 进程上下文包含更多信息,如整个地址空间、打开的文件描述符等。进程上下文切换通常比线程或协程上下文切换开销大,因为涉及更多资源和内存管理。
上下文切换
上下文切换(Context Switching)是指从一个任务切换到另一个任务的过程。此过程涉及保存当前任务的上下文,并加载下一个任务的上下文。上下文切换有一定的开销,因为涉及寄存器的保存和恢复、内核模式和用户模式之间的切换等。