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_caller为true,那么实际上需要额外创建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;
}
}