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

276 阅读22分钟

Scheduler.h

#ifndef __SYLAR_SCHEDULER_H__
#define __SYLAR_SCHEDULER_H__

#include <memory>
#include "fiber.h"
//#include 
#include "thread.h"
#include <vector>
#include <list>

namespace sylar{

// 用于管理和调度多个协程在指定数量的线程上运行
class Scheduler{
public:
    typedef std::shared_ptr<Scheduler> ptr;
    typedef Mutex MutexType;
  
    Scheduler(size_t threads = 1, bool use_caller = true, const std::string& name = "");
    virtual ~Scheduler();

    const std::string& getName() const { return m_name;}
    // 返回当前线程正在运行的调度器
    static Scheduler* GetThis();
    // 返回当前线程的主协程
    static Fiber* GetMainFiber();

    void start();   // 启动调度器
    void stop();    // 停止调度器

    /*---------- schedule() 方法模板:将一个协程或回调函数安排到调度器队列中,并指定可选的目标线程ID----------*/
    template<class FiberOrCb>
    // fc: 要调度的任务   thread:指定将任务分配到的目标线程,-1表示不指定线程
    void schedule(FiberOrCb fc, int thread = -1)
    {
        bool need_tickle = false;   // 是否需要唤醒一个线程来处理新的任务
        {
            MutexType::Lock lock(m_mutex);
            // 此函数将任务添加到队列中,并判断是否需要唤醒线程,
            // 如果队列在添加任务前是空的,则need_tickle会被设置为true
            need_tickle = scheduleNoLock(fc, thread);
        }
        if(need_tickle)
        {
            tickle();
        }
    }

    // 将一组任务(协程或回调函数)添加到调度器的任务队列中
    template<class InputIterator>
    void schedule(InputIterator begin, InputIterator end)
    {
        bool need_tickle = false;
        {
            MutexType::Lock lock(m_mutex);
            while(begin != end)     // 遍历任务集合
            {
                // 任何一次 scheduleNoLock 返回 true,则 need_tickle 将被置为 true
                need_tickle = scheduleNoLock(&*begin, -1) || need_tickle;
                ++begin;
            }
            if(need_tickle)
            {
                tickle();
            }
        }
    }

protected:
    virtual void tickle();      // 唤醒一个线程以处理新的任务  |  通知协程调度器有任务了
    void run();                 // 主循环,负责从任务队列中提取和执行任务
    virtual bool stopping();    // 返回是否可以停止
    virtual void idle();        // 定义空闲线程的行为

    void setThis();             // 设置当前的协程调度器

    bool hasIdleThreads() {return m_idleThreadCount > 0;}       // 是否有空闲线程

private:
    template<class FiberOrCb>
    bool scheduleNoLock(FiberOrCb fc, int thread)
    {
        // m_fibers 为空时返回 true
        bool need_tickle = m_fibers.empty();
        FiberAndThread ft(fc, thread);
        if(ft.fiber || ft.cb)
        {
            m_fibers.push_back(ft);
        }
        return need_tickle; // 为真表示需要唤醒线程
    }

private:
    // 此结构体用于表示要调度的的任务
    struct FiberAndThread{
        Fiber::ptr fiber;
        std::function<void()> cb;
        int thread;

        FiberAndThread(Fiber::ptr f, int thr): fiber(f), thread(thr)
        {

        }

        FiberAndThread(Fiber::ptr* f, int thr):  thread(thr)
        {
            fiber.swap(*f);
        }

        FiberAndThread(std::function<void()> f, int thr): cb(f), thread(thr)
        {

        }

        FiberAndThread(std::function<void()>* f, int thr): thread(thr)
        {
            cb.swap(*f);
        }

        FiberAndThread() : thread(-1)
        {

        }

        void reset()
        {
            fiber = nullptr;
            cb = nullptr;
            thread = -1;
        }
    };

private:
    MutexType m_mutex;                  // 互斥锁
    std::vector<Thread::ptr> m_threads; // 存储调度器管理的线程对象
    std::list<FiberAndThread> m_fibers; // 存储等待调度的协程或回调任务
    Fiber::ptr m_rootFiber;             // 调度器的主协程
    std::string m_name;

protected:
    std::vector<int> m_threadIds;       // 所有线程的 id
    size_t m_threadCount = 0;           // 总线程数
    std::atomic<size_t> m_activeThreadCount = {0};  // 活跃线程的数量
    std::atomic<size_t> m_idleThreadCount = {0};    // 空闲线程的数量
    bool m_stopping = true;
    bool m_autoStop = false;
    int m_rootThread = 0;
};

}

#endif


相关 变量 及 函数 解析

(一)两个线程局部变量

// 线程局部变量
static thread_local Scheduler* t_scheduler = nullptr;   // 当前线程的调度器
static thread_local Fiber* t_fiber = nullptr;           // 当前线程的主协程
  • t_scheduler 是一个指向 Scheduler 类型的指针,表示当前线程正在使用的调度器实例。确保每个线程都能通过 t_scheduler 快速访问它所使用的调度器。这样,在调度器的不同方法中,可以方便地获取当前线程正在使用的调度器,而不需要通过全局变量或参数传递。

    • 代码中可以调用 Scheduler::GetThis() 方法来访问当前线程的 t_scheduler
    // 当前线程的调度器
    Scheduler *Scheduler::GetThis()
    {
        return t_scheduler;
    }
    
  • t_fiber 是一个指向 Fiber 类型的指针,表示当前线程的主协程。用来跟踪和访问当前线程正在运行的主协程,方便调度器在同一个线程中切换协程。

(二)构造函数

/**
 * ************************************************************************
 * @brief   描述
 * @param   threads     指定用于调度的线程数量
 * @param   use_caller  是否使用调用线程作为调度线程的一部分
 * @param   name        调度器名称
 * @date    2024-11-18
 * ************************************************************************
 */
Scheduler::Scheduler(size_t threads, bool use_caller, const std::string & name)
    : m_name(name)
{
    // 断言检查,确保创建调度器时至少有一个线程数。
    SYLAR_ASSERT(threads > 0);

    // 如果 use_caller 为 true,则将调用线程作为调度器的一部分来使用。
    if (use_caller)
    {
        // 获取当前线程正在执行的协程。确保调用线程已经有了协程环境
        sylar::Fiber::GetThis();

        // 将线程数减少一个,因为调用线程会作为调度器的一个工作线程。
        --threads;

        // 断言检查,确保当前线程尚未绑定到其他 Scheduler。
        SYLAR_ASSERT(GetThis() == nullptr);

        // 将 t_scheduler 设置为当前调度器实例。
        t_scheduler = this;

        // 创建 m_rootFiber,这是一个主协程,它绑定 Scheduler::run 方法,用于启动调度器的任务执行。
        // 参数 0 表示该协程没有特定的栈大小;true 表示使用调用线程为工作线程。
        m_rootFiber.reset(new Fiber(std::bind(&Scheduler::run, this), 0, true));

        // 设置当前线程的名称为调度器的名称。
        sylar::Thread::SetName(m_name);

        // 将主协程指针保存到 t_fiber
        t_fiber = m_rootFiber.get();

        // 获取当前线程的 ID,并将其存储为 m_rootThread,即调用线程的 ID。
        m_rootThread = sylar::GetThreadId();

        // 将调用线程 ID 添加到 m_threadIds 列表中,用于记录调度器管理的线程 ID。
        m_threadIds.push_back(m_rootThread);
    }
    else
    {
        // 如果 use_caller 为 false,将 m_rootThread 设置为 -1,表示没有使用调用线程。
        m_rootThread = -1;
    }

    // 设置 m_threadCount 为剩余的线程数量,表示调度器要管理的工作线程数。
    m_threadCount = threads;
}

  • use_caller 表示是否使用调用线程作为调度器的一部分来运行任务。
  • --threads :首先 threads 表示需要创建的线程数量,如果 use_callertrue,那么实际上需要额外创建 threads - 1 个线程,所以需要 --threads

(三)stopping()

stopping() 函数用于检查协程调度器是否可以安全地停止工作

// 调度器是否可以安全停止工作
bool Scheduler::stopping()
{
   MutexType::Lock lock(m_mutex);
   // 下列条件全部满足才可停止
   return m_autoStop && m_stopping && m_fibers.empty() && m_activeThreadCount == 0;
}

(四)idle()

此函数定义了调度器中线程在空闲时的行为。调度器中,当所有当前任务都已完成、队列中没有新任务时,线程进入空闲状态。这是为了避免线程不断占用 CPU 资源,等待新任务的调度。

// 调度器中线程在空闲时的行为
void Scheduler::idle()
{
    SYLAR_LOG_INFO(g_logger) << "idle";
    while(!stopping())  // 调度器未满足停止条件
    {
        sylar::Fiber::YieldToHold();    // 将当前协程的状态设置为 暂停
    }
}
  • 为什么线程在空闲时的行为却是在调用协程方法 YieldToHold() 在协程调度器中,每个线程的执行都是通过协程来控制的。使用 YieldToHold(),线程进入挂起状态但不会结束,这样可以随时恢复执行新的协程任务,而无需重新启动线程。

(五)析构函数

Scheduler::~Scheduler()
{
    SYLAR_ASSERT(m_stopping);   // 调度器正在停止
    if(GetThis() == this)       // 检查当前线程的调度器是否是正在析构的这个调度器
    {
        t_scheduler = nullptr;
    }
}
  • t_scheduler = nullptr; :清理当前线程与调度器之间的关联,避免悬挂指针或重复访问已销毁的对象。

(六)start()

启动调度器,创建线程来运行调度器的任务。确保调度器在启动时正确初始化线程池,并将调度逻辑绑定到每个线程,使其可以并行处理任务

// 启动调度器
void Scheduler::start()
{
    MutexType::Lock lock(m_mutex);  // 加锁以保证线程安全

    if (!m_stopping)                // 若调度器已经停止,直接返回
    {
        return;
    }

    m_stopping = false;             // 标记调度器正在运行

    SYLAR_ASSERT(m_threads.empty());    // 确定线程队列在启动前是空的,避免重复启动

    m_threads.resize(m_threadCount);    // 初始化线程队列

    for (size_t i = 0; i < m_threadCount; i++)
    {
        // 创建新的线程并将其与 Scheduler::run 绑定,启动时执行调度逻辑
        m_threads[i].reset(new Thread(std::bind(&Scheduler::run, this),
                                      m_name + "_" + std::to_string(i)));
        m_threadIds.push_back(m_threads[i]->getId());   // 存储线程ID
    }
    // lock.unlock();
}

(七)run()

协程调度器的核心,它在每个线程中被调用,管理调度和执行协程及任务的逻辑

void Scheduler::run()
{
    setThis();  // t_scheduler = this

    // 如果当前线程不是主线程,调用 Fiber::GetThis() 以确保当前线程有协程环境
    if (sylar::GetThreadId() != m_rootThread)
    {
        t_fiber = Fiber::GetThis().get();
    }

    // 创建一个空闲协程,当没有其他任务时执行
    Fiber::ptr idle_fiber(new Fiber(std::bind(&Scheduler::idle, this)));
    Fiber::ptr cb_fiber;    // 用来执行回调函数的协程

    FiberAndThread ft;      // 临时存储从任务队列中提取的任务

    // 无限循环用于不断调度任务 
    while (true)
    {
        ft.reset();             // 重置任务结构体
        bool tickle_me = false; // 标记是否需要唤醒其他线程
        bool is_active = false; // 标记是否有活跃的任务

        {
            // 锁住任务队列
            MutexType::Lock lock(m_mutex);  
            auto it = m_fibers.begin();     // 指向任务队列的开头
            
            while (it != m_fibers.end())    // 遍历任务队列,寻找合适的任务
            {
                // 如果任务指定了线程,但不是当前线程,跳过该任务
                if (it->thread != -1 && it->thread != sylar::GetThreadId())
                {
                    it++;
                    tickle_me = true;       // 标记需要唤醒其他线程来处理任务
                    continue;
                }
                SYLAR_ASSERT(it->fiber || it->cb);  // 确保任务包含有效的协程或回调函数

                // 如果协程正在执行,跳过
                if (it->fiber && it->fiber->getState() == Fiber::EXEC)
                {
                    it++;
                    continue;
                }

                // 找到可执行的任务,将其取出并从队列中删除
                ft = *it;
                m_fibers.erase(it);
                ++m_activeThreadCount;  // 增加活跃线程数
                is_active = true;
                break;                  // 跳出循环,准备执行任务
            }
        }

        // 如果需要唤醒其他线程,调用 tickle() 方法
        if (tickle_me)
        {
            tickle();
        }

        // 如果取出的任务是协程且状态不是终止或异常,执行协程
        if (ft.fiber && (ft.fiber->getState() != Fiber::TERM && ft.fiber->getState() != Fiber::EXCEPT))
        {
            ft.fiber->swapIn();     // 切换到协程执行
            --m_activeThreadCount;  // 任务执行结束后减少活跃线程数

            // 如果协程在执行后状态变为 READY,将其重新调度
            if (ft.fiber->getState() == Fiber::READY)
            {
                schedule(ft.fiber);
            }
 
            else if (ft.fiber->getState() != Fiber::TERM && ft.fiber->getState() != Fiber::EXCEPT)
            {
                // 如果协程没有终止或异常,设置状态为 Hold
                ft.fiber->m_state = Fiber::HOLD;
            }

            ft.reset();     // 重置任务结构体
        }

        // 如果取出的任务是回调函数,创建或重置 cb_fiber 并执行
        else if (ft.cb) 
        {
            if (cb_fiber)
            {
                cb_fiber->reset(ft.cb);             // 重置现有协程的回调函数
            }
            else
            {
                cb_fiber.reset(new Fiber(ft.cb));   // 创建新的协程
            }

            ft.reset(); 

            cb_fiber->swapIn();                     // 切换到协程执行回调
            --m_activeThreadCount;                  // 任务执行结束后减少活跃线程数

            // 如果回调执行结束后状态为 Ready,将其重新调度
            if (cb_fiber->getState() == Fiber::READY)
            {
                schedule(cb_fiber);
                cb_fiber.reset(); // 重置指针以避免重复使用
            }
            // 如果协程状态为异常或终止,释放协程资源
            else if (cb_fiber->getState() == Fiber::EXCEPT || cb_fiber->getState() == Fiber::TERM)
            {
                cb_fiber->reset(nullptr);
            }
            else
            {
                // 设置状态为 HOLD 并重置指针
                cb_fiber->m_state = Fiber::HOLD;
                cb_fiber.reset();
            }
        }

        // 如果没有任务执行,执行空闲协程
        else 
        {
            if (is_active)
            {
                --m_activeThreadCount;  
                continue;   // 返回循环顶部重新获取任务
            }

            // 如果空闲协程已结束,记录日志并退出
            if (idle_fiber->getState() == Fiber::TERM)
            {
                SYLAR_LOG_INFO(g_logger) << "idle fiber term";
                break;
            }

            ++m_idleThreadCount;    // 增加空闲协程数
            idle_fiber->swapIn();   // 切换到空闲协程
            --m_idleThreadCount;    // 减少空闲协程数
            
            // 如果空闲协程执行后状态不是终止或异常,设置为 HOLD
            if (idle_fiber->getState() != Fiber::TERM && idle_fiber->getState() != Fiber::EXCEPT)
            {
                idle_fiber->m_state = Fiber::HOLD;
            }
        }
    }
}
  • while 循环寻找当前线程可以执行的任务

    while (it != m_fibers.end())    // 遍历任务队列,寻找合适的任务
    {
        // 如果任务指定了线程,但不是当前线程,跳过该任务
        if (it->thread != -1 && it->thread != sylar::GetThreadId())
        {
            it++;
            tickle_me = true;       // 标记需要唤醒其他线程来处理任务
            continue;
        }
        SYLAR_ASSERT(it->fiber || it->cb);  // 确保任务包含有效的协程或回调函数
    
        // 如果任务是一个协程且正在执行,跳过该任务
        if (it->fiber && it->fiber->getState() == Fiber::EXEC)
        {
            it++;
            continue;
        }
    
        // 找到可执行的任务,将其取出并从队列中删除
        ft = *it;
        m_fibers.erase(it);
        ++m_activeThreadCount;  // 增加活跃线程数
        is_active = true;
        break;                  // 跳出循环,结束任务查找过程,准备执行任务
    }
    

    如果任务指定了一个特定线程而当前线程不是该任务的目标线程,则跳过该任务(通过 it++)并将 tickle_me 设为 true,表示其他线程需要被唤醒来处理这些任务。

  • ft.fiber->swapIn(); 以及 cb_fiber->swapIn();

    • Scheduler::run() 中,当 swapIn() 被调用时,程序切换到 Fiber 协程的上下文,这时 Fiber::MainFunc() 会开始执行。如果是第一次切换到该协程,MainFunc() 会调用协程的回调函数 m_cb()
    • 执行 swapIn() 会在 MainFunc() 中执行,这是因为在创建协程对象时(通过 makecontext()),上下文被绑定到了 Fiber::MainFunc() 入口函数。
  • 协程执行任务后的不同状态

    // 如果回调执行结束后状态为 Ready,将其重新调度
    if (cb_fiber->getState() == Fiber::READY)
    {
        schedule(cb_fiber);
        cb_fiber.reset(); // 重置指针以避免重复使用
    }
    // 如果协程状态为异常或终止,释放协程资源
    else if (cb_fiber->getState() == Fiber::EXCEPT || cb_fiber->getState() == Fiber::TERM)
    {
        cb_fiber->reset(nullptr);
    }
    else
    {
        // 设置状态为 HOLD 并重置指针
        cb_fiber->m_state = Fiber::HOLD;
        cb_fiber.reset();
    }
    
    • 重新调度指的是将一个任务(通常是协程或回调函数)再次加入到调度器的任务队列中,以便后续继续执行。
    • 如果协程在执行后状态为 READY,说明任务尚未完成,需要稍后再运行。
    • 任务执行完成后,协程的状态应该是 TERM,表示协程已经成功执行完毕,其生命周期基本结束。
  • 没有任务需要执行时,执行空闲协程

    // 如果没有任务执行,执行空闲协程
      else 
      {
          if (is_active)
          {
              --m_activeThreadCount;  
              continue;   // 返回循环顶部重新获取任务
          }
    
          // 如果空闲协程已结束,记录日志并退出
          if (idle_fiber->getState() == Fiber::TERM)
          {
              SYLAR_LOG_INFO(g_logger) << "idle fiber term";
              break;
          }
    
          ++m_idleThreadCount;    // 增加空闲协程数
          idle_fiber->swapIn();   // 切换到空闲协程
          --m_idleThreadCount;    // 减少空闲协程数
    
          // 如果空闲协程执行后状态不是终止或异常,设置为 HOLD
          if (idle_fiber->getState() != Fiber::TERM && idle_fiber->getState() != Fiber::EXCEPT)
          {
              idle_fiber->m_state = Fiber::HOLD;
          }
      }
    
    if (is_active)
    {
        --m_activeThreadCount;  
        continue;   // 返回循环顶部重新获取任务
    }
    
    • 如果当前线程处于活跃状态(is_active == true),说明之前可能已经执行过某些任务。

    • 此时,减少活跃线程计数(m_activeThreadCount),然后继续从任务队列中获取下一个任务。

    • 通过让活跃线程优先尝试获取任务,可以减少线程切换的频率。

    // 如果空闲协程已结束,记录日志并退出
    if (idle_fiber->getState() == Fiber::TERM)
    {
        SYLAR_LOG_INFO(g_logger) << "idle fiber term";
        break;
    }
    
    • 如果空闲协程的状态是 TERM,表示空闲协程任务已经完成并正常退出。

    • 记录日志,然后 退出调度循环break)。

    • 空闲协程退出可能是由于整个调度器正在关闭,所有任务都已处理完毕。

    // 如果空闲协程执行后状态不是终止或异常,设置为 HOLD
    if (idle_fiber->getState() != Fiber::TERM && idle_fiber->getState() != Fiber::EXCEPT)
    {
        idle_fiber->m_state = Fiber::HOLD;
    }
    
    • 如果空闲协程没有终止(TERM)或发生异常(EXCEPT),说明协程仍然可以被调度。
    • 空闲协程的状态设置为 HOLD,表示协程挂起,等待下次使用

(八)stop()

停止协程调度器,释放相关的线程和协程资源。

void Scheduler::stop()
{
    // 设置自动停止标志,表明调度器进入停止流程。
    m_autoStop = true;

    // 检查是否有主协程,且线程数量为 0,并且主协程状态为终止(TERM)或初始化(INIT)。
    if (m_rootFiber && m_threadCount == 0 && (m_rootFiber->getState() == Fiber::TERM
                                               || m_rootFiber->getState() == Fiber::INIT))
    {
        // 打印日志,标记调度器已停止。
        SYLAR_LOG_INFO(g_logger) << this << " stopped";
        
        // 设置 m_stopping 为 true 表示调度器正在停止。
        m_stopping = true;

        // 检查是否已满足停止条件,如果是,则返回,不再继续执行。
        if (stopping())
        {
            return;
        }
    }

    // 如果调度器由主线程调用,则当前调度器实例应与 t_scheduler 相同。
    if (m_rootThread != -1)
    {
        SYLAR_ASSERT(GetThis() == this);
    }
    else
    {
        // 否则,当前线程的调度器实例不应是 this。
        SYLAR_ASSERT(GetThis() != this);
    }

    // 设置 m_stopping 为 true,表示调度器正在停止。
    m_stopping = true;

    // 通知所有工作线程调度器正在停止,通过调用 tickle() 唤醒线程。
    for (size_t i = 0; i < m_threadCount; i++)
    {
        tickle();
    }

    // 如果有主协程,额外唤醒一次以确保停止。
    if (m_rootFiber)
    {
        tickle();
    }

    // 检查并调用主协程以便清理或完成剩余任务。
    if (m_rootFiber)
    {
        // 检查是否还未满足停止条件,如果未满足,则继续调用 m_rootFiber。
        if (!stopping())
        {
            m_rootFiber->call();
        }
    }

    // 创建一个线程指针向量,用于存储当前调度器中的所有线程。
    std::vector<Thread::ptr> thrs;
    {
        // 加锁以安全地交换 m_threads 和临时向量 thrs,然后解锁。
        MutexType::Lock lock(m_mutex);
        thrs.swap(m_threads);
    }

    // 等待所有线程完成工作并结束。
    for (auto& i : thrs)
    {
        i->join();
    }
}
  • 逻辑总结

    • 停止标志:首先标记调度器即将停止。

    • 任务清理:确保剩余的任务(包括根协程)被执行。

    • 线程管理:唤醒所有线程,避免线程长时间等待。

    • 资源释放:清理所有线程,确保调度器停止后没有遗留的资源。

    • 线程协作:如果调度器无法立即停止,根协程和其他线程会协同完成剩余任务,直到可以安全退出。

  • 验证当前线程与调度器实例的对应关系

// 如果调度器由主线程调用,则当前调度器实例应与 t_scheduler 相同。
if (m_rootThread != -1)
{
    SYLAR_ASSERT(GetThis() == this);
}
else
{
    // 否则,当前线程的调度器实例不应是 this。
    SYLAR_ASSERT(GetThis() != this);
}

验证当前线程与调度器实例的对应关系,确保主线程使用主调度器,其他线程不会误用。

提供防御性检查,防止多线程环境中的潜在错误。

帮助调试和定位调度器使用中的上下文问题。

  • 调用 tickle() 方法
// 通知所有工作线程调度器正在停止,通过调用 tickle() 唤醒线程。
for (size_t i = 0; i < m_threadCount; i++)
{
    tickle();
}

// 如果有主协程,额外唤醒一次
if (m_rootFiber)
{
    tickle();
}
  • 第一部分 (for (size_t i = 0; i < m_threadCount; i++) { tickle(); }):

    • 目的: 通过循环调用 tickle(),唤醒所有的工作线程。
    • 背景: 在调度器停止的过程中,需要确保每个工作线程能够被唤醒并终止当前的任务。tickle() 可以让处于阻塞、等待或者休眠状态的线程重新被唤醒,从而使其可以退出或者进行必要的资源清理。
    • m_threadCount: 表示工作线程的数量,这里通过循环唤醒所有线程。
  • 第二部分 (if (m_rootFiber) { tickle(); }):

    • 目的: 如果根协程 (m_rootFiber) 存在,唤醒它。
    • 背景: 根协程是调度器的核心协程,它通常代表主线程的执行流。即使主线程也需要执行一些清理任务,确保主线程也能通过 tickle() 被唤醒,以完成停止过程。
    • 条件判断: m_rootFiber 存在时,即调度器有根协程在运行时,唤醒主协程进行清理或退出操作。

Scheduler.cpp

#include "scheduler.h"
#include "log.h"
#include "macro.h"

namespace sylar
{
static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");

// 线程局部变量
static thread_local Scheduler* t_scheduler = nullptr;   // 当前线程的调度器
static thread_local Fiber* t_fiber = nullptr;           // 当前线程的主协程

/**
 * ************************************************************************
 * @brief   描述
 * @param   threads     指定用于调度的线程数量
 * @param   use_caller  是否使用调用线程作为调度线程的一部分
 * @param   name        调度器名称
 * @date    2024-11-18
 * ************************************************************************
 */
Scheduler::Scheduler(size_t threads, bool use_caller, const std::string & name)
    : m_name(name)
{
    // 断言检查,确保创建调度器时至少有一个线程数。
    SYLAR_ASSERT(threads > 0);

    // 如果 use_caller 为 true,则将调用线程作为调度器的一部分来使用。
    if (use_caller)
    {
        // 获取当前线程正在执行的协程。确保调用线程已经有了协程环境
        sylar::Fiber::GetThis();

        // 将线程数减少一个,因为调用线程会作为调度器的一个工作线程。
        --threads;

        // 断言检查,确保当前线程尚未绑定到其他 Scheduler。
        SYLAR_ASSERT(GetThis() == nullptr);

        // 将 t_scheduler 设置为当前调度器实例。
        t_scheduler = this;

        // 创建 m_rootFiber,这是一个主协程,它绑定 Scheduler::run 方法,用于启动调度器的任务执行。
        // 参数 0 表示该协程没有特定的栈大小;true 表示使用调用线程为工作线程。
        m_rootFiber.reset(new Fiber(std::bind(&Scheduler::run, this), 0, true));

        // 设置当前线程的名称为调度器的名称。
        sylar::Thread::SetName(m_name);

        // 将主协程指针保存到 t_fiber
        t_fiber = m_rootFiber.get();

        // 获取当前线程的 ID,并将其存储为 m_rootThread,即调用线程的 ID。
        m_rootThread = sylar::GetThreadId();

        // 将调用线程 ID 添加到 m_threadIds 列表中,用于记录调度器管理的线程 ID。
        m_threadIds.push_back(m_rootThread);
    }
    else
    {
        // 如果 use_caller 为 false,将 m_rootThread 设置为 -1,表示没有使用调用线程。
        m_rootThread = -1;
    }

    // 设置 m_threadCount 为剩余的线程数量,表示调度器要管理的工作线程数。
    m_threadCount = threads;
}


Scheduler::~Scheduler()
{
    SYLAR_ASSERT(m_stopping);   // 调度器正在停止
    if(GetThis() == this)       // 检查当前线程的调度器是否是正在析构的这个调度器
    {
        t_scheduler = nullptr;
    }
}


// 当前线程正在运行的调度器
Scheduler *Scheduler::GetThis()
{
    return t_scheduler;
}

// 当前线程的主协程
Fiber *Scheduler::GetMainFiber()
{
    return t_fiber;
}

// 启动调度器
void Scheduler::start()
{
    MutexType::Lock lock(m_mutex);  // 加锁以保证线程安全

    if (!m_stopping)                // 若调度器已经停止,直接返回
    {
        return;
    }

    m_stopping = false;             // 标记调度器正在运行

    SYLAR_ASSERT(m_threads.empty());    // 确定线程队列在启动前是空的,避免重复启动

    m_threads.resize(m_threadCount);    // 初始化线程队列

    for (size_t i = 0; i < m_threadCount; i++)
    {
        // 创建新的线程并将其与 Scheduler::run 绑定,启动时执行调度逻辑
        m_threads[i].reset(new Thread(std::bind(&Scheduler::run, this),
                                      m_name + "_" + std::to_string(i)));
        m_threadIds.push_back(m_threads[i]->getId());   // 存储线程ID
    }
    // lock.unlock();
}


void Scheduler::stop()
{
    // 设置自动停止标志,表明调度器进入停止流程。
    m_autoStop = true;

    // 检查是否有主协程,且线程数量为 0,并且主协程状态为终止(TERM)或初始化(INIT)。
    if (m_rootFiber && m_threadCount == 0 && (m_rootFiber->getState() == Fiber::TERM
                                               || m_rootFiber->getState() == Fiber::INIT))
    {
        // 打印日志,标记调度器已停止。
        SYLAR_LOG_INFO(g_logger) << this << " stopped";
        
        // 设置 m_stopping 为 true 表示调度器正在停止。
        m_stopping = true;

        // 检查是否已满足停止条件,如果是,则返回,不再继续执行。
        if (stopping())
        {
            return;
        }
    }

    // 如果调度器由主线程调用,则当前调度器实例应与 t_scheduler 相同。
    if (m_rootThread != -1)
    {
        SYLAR_ASSERT(GetThis() == this);
    }
    else
    {
        // 否则,当前线程的调度器实例不应是 this。
        SYLAR_ASSERT(GetThis() != this);
    }

    // 设置 m_stopping 为 true,表示调度器正在停止。
    m_stopping = true;

    // 通知所有工作线程调度器正在停止,通过调用 tickle() 唤醒线程。
    for (size_t i = 0; i < m_threadCount; i++)
    {
        tickle();
    }

    // 如果有主协程,额外唤醒一次以确保停止。
    if (m_rootFiber)
    {
        tickle();
    }

    // 检查并调用主协程以便清理或完成剩余任务。
    if (m_rootFiber)
    {
        // 检查是否还未满足停止条件,如果未满足,则继续调用 m_rootFiber。
        if (!stopping())
        {
            m_rootFiber->call();
        }
    }

    // 创建一个线程指针向量,用于存储当前调度器中的所有线程。
    std::vector<Thread::ptr> thrs;
    {
        // 加锁以安全地交换 m_threads 和临时向量 thrs,然后解锁。
        MutexType::Lock lock(m_mutex);
        thrs.swap(m_threads);
    }

    // 等待所有线程完成工作并结束。
    for (auto& i : thrs)
    {
        i->join();
    }
}



void Scheduler::run()
{
    setThis();  // t_scheduler = this

    // 如果当前线程不是主线程,调用 Fiber::GetThis() 以确保当前线程有协程环境
    if (sylar::GetThreadId() != m_rootThread)
    {
        t_fiber = Fiber::GetThis().get();
    }

    // 创建一个空闲协程,当没有其他任务时执行
    Fiber::ptr idle_fiber(new Fiber(std::bind(&Scheduler::idle, this)));
    Fiber::ptr cb_fiber;    // 用来执行回调函数的协程

    FiberAndThread ft;      // 临时存储从任务队列中提取的任务

    // 无限循环用于不断调度任务 
    while (true)
    {
        ft.reset();             // 重置任务结构体
        bool tickle_me = false; // 标记是否需要唤醒其他线程
        bool is_active = false; // 标记是否有活跃的任务

        {
            // 锁住任务队列
            MutexType::Lock lock(m_mutex);  
            auto it = m_fibers.begin();     // 指向任务队列的开头
            
            while (it != m_fibers.end())    // 遍历任务队列,寻找合适的任务
            {
                // 如果任务指定了线程,但不是当前线程,跳过该任务
                if (it->thread != -1 && it->thread != sylar::GetThreadId())
                {
                    it++;
                    tickle_me = true;       // 标记需要唤醒其他线程来处理任务
                    continue;
                }
                SYLAR_ASSERT(it->fiber || it->cb);  // 确保任务包含有效的协程或回调函数

                // 如果任务是一个协程且正在执行,跳过该任务
                if (it->fiber && it->fiber->getState() == Fiber::EXEC)
                {
                    it++;
                    continue;
                }

                // 找到可执行的任务,将其取出并从队列中删除
                ft = *it;
                m_fibers.erase(it);
                ++m_activeThreadCount;  // 增加活跃线程数
                is_active = true;
                break;                  // 跳出循环,结束任务查找过程,准备执行任务
            }
        }

        // 如果需要唤醒其他线程,调用 tickle() 方法
        if (tickle_me)
        {
            tickle();
        }

        // 如果取出的任务是协程且状态不是终止或异常,执行协程
        if (ft.fiber && (ft.fiber->getState() != Fiber::TERM && ft.fiber->getState() != Fiber::EXCEPT))
        {
            ft.fiber->swapIn();     // 切换到协程执行
            --m_activeThreadCount;  // 任务执行结束后减少活跃线程数

            // 如果协程在执行后状态变为 READY,将其重新调度
            if (ft.fiber->getState() == Fiber::READY)
            {
                schedule(ft.fiber);
            }
 
            else if (ft.fiber->getState() != Fiber::TERM && ft.fiber->getState() != Fiber::EXCEPT)
            {
                // 如果协程没有终止或异常,设置状态为 Hold
                ft.fiber->m_state = Fiber::HOLD;
            }

            ft.reset();     // 重置任务结构体
        }

        // 如果取出的任务是回调函数,创建或重置 cb_fiber 并执行
        else if (ft.cb) 
        {
            if (cb_fiber)
            {
                cb_fiber->reset(ft.cb);             // 重置现有协程的回调函数
            }
            else
            {
                cb_fiber.reset(new Fiber(ft.cb));   // 创建新的协程
            }

            ft.reset(); 

            cb_fiber->swapIn();                     // 切换到协程执行回调
            --m_activeThreadCount;                  // 任务执行结束后减少活跃线程数

            // 如果回调执行结束后状态为 Ready,将其重新调度
            if (cb_fiber->getState() == Fiber::READY)
            {
                schedule(cb_fiber);
                cb_fiber.reset(); // 重置指针以避免重复使用
            }
            // 如果协程状态为异常或终止,释放协程资源
            else if (cb_fiber->getState() == Fiber::EXCEPT || cb_fiber->getState() == Fiber::TERM)
            {
                cb_fiber->reset(nullptr);
            }
            else
            {
                // 设置状态为 HOLD 并重置指针
                cb_fiber->m_state = Fiber::HOLD;
                cb_fiber.reset();
            }
        }

        // 如果没有任务执行,执行空闲协程
        else 
        {
            if (is_active)
            {
                --m_activeThreadCount;  
                continue;   // 返回循环顶部重新获取任务
            }

            // 如果空闲协程已结束,记录日志并退出
            if (idle_fiber->getState() == Fiber::TERM)
            {
                SYLAR_LOG_INFO(g_logger) << "idle fiber term";
                break;
            }

            ++m_idleThreadCount;    // 增加空闲协程数
            idle_fiber->swapIn();   // 切换到空闲协程
            --m_idleThreadCount;    // 从 idle_fiber 返回后,减少空闲协程数
            
            // 如果空闲协程执行后状态不是终止或异常,设置为 HOLD
            if (idle_fiber->getState() != Fiber::TERM && idle_fiber->getState() != Fiber::EXCEPT)
            {
                idle_fiber->m_state = Fiber::HOLD;
            }
        }
    }
}


void Scheduler::tickle()
{
    SYLAR_LOG_INFO(g_logger) << "tickle";
}

// 调度器是否可以安全停止工作
bool Scheduler::stopping()
{
   MutexType::Lock lock(m_mutex);
   // 下列条件全部满足才可停止
   return m_autoStop && m_stopping && m_fibers.empty() && m_activeThreadCount == 0;
}

// 调度器中线程在空闲时的行为
void Scheduler::idle()
{
    SYLAR_LOG_INFO(g_logger) << "idle";
    while(!stopping())  // 调度器未满足停止条件
    {
        sylar::Fiber::YieldToHold();    // 将当前协程的状态设置为 暂停
    }
}

void Scheduler::setThis()
{
    t_scheduler = this;
}

}