C++项目实战——sylar服务器框架:协程模块

351 阅读20分钟

什么是协程

什么是协程(一次性讲清)-CSDN博客

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 就会递增,为协程分配唯一标识符。
  • 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 标准库的 mallocfree 函数,用于分配和释放内存块。在这里负责分配和释放协程栈的内存。

(三)私有默认构造函数

// 线程局部变量,表示当前正在执行的协程指针。
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_fibernullptr,说明当前线程没有一个关联的协程,于是创建一个新的主协程对象 main_fiber

  • main_fibershared_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();             // 切换当前协程的上下文,交出控制权
}

(十三)MainFuncCallerMainFunc()

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_callerfalse 时表示协程为标准协程,协程执行完任务后挂起,切换到 Scheduler::GetMainFiber
    • use_callertrue 时表示协程为调度协程,协程执行完任务后挂起,切换到 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)在计算机科学中指的是程序执行时所需的所有状态信息。在协程、线程和进程的切换中,保存和恢复上下文是至关重要的步骤。上下文包含了用于保存程序当前运行状态的信息,使得程序在被中断后能够恢复执行而不会丢失状态。

上下文通常包括以下几部分:

  1. CPU 寄存器:如程序计数器(PC,Program Counter)、栈指针(SP,Stack Pointer)和通用寄存器等。这些寄存器保存当前执行的位置和临时数据。
  2. 栈信息:包括调用栈、局部变量和返回地址等信息。
  3. 程序状态字:如状态寄存器中保存的标志位,用于指示程序当前状态(如中断标志、条件标志等)。
  4. 内存空间:有时,尤其是进程上下文,还包括了整个虚拟内存空间的信息。
  5. 其他资源:对于进程,可能还包括文件描述符、信号处理信息等。

上下文的应用场景

上下文切换是实现多任务处理的基础。不同场景中对上下文的定义和管理略有不同:

  1. 协程

    • 协程上下文保存了当前协程的执行状态,包括寄存器内容、栈指针等信息,使得协程可以在挂起后继续从中断点恢复执行。
    • 例如,在实现协程库中,调用 getcontextsetcontext 等系统调用来保存和恢复上下文。
  2. 线程

    • 每个线程都有自己的上下文,包含寄存器状态和栈空间。当线程被调度器暂停时,当前线程的上下文会被保存,以便在下次恢复执行时继续运行。
  3. 进程

    • 进程上下文包含更多信息,如整个地址空间、打开的文件描述符等。进程上下文切换通常比线程或协程上下文切换开销大,因为涉及更多资源和内存管理。

上下文切换

上下文切换(Context Switching)是指从一个任务切换到另一个任务的过程。此过程涉及保存当前任务的上下文,并加载下一个任务的上下文。上下文切换有一定的开销,因为涉及寄存器的保存和恢复、内核模式和用户模式之间的切换等。