上一章节介绍了线程与协程后我们终于来到了sylar的核心关键——协程调度器。协程调度器是sylar服务器能够实现高并发的核心关键如果少了这一关键性能会大打折扣。首先我想先从类成员开始介绍调度器我们可以看到调度器内部封装的是一个线程池说到线程池大家是不是很熟悉没错生产消费者模型任务队列调度器正是在此基础上衍生出来的调度器的模式是N-M模式N就是线程调度器会创建多个线程取决于用户为协程提供处理事件的独立环境。M就是协程也就是任务内部包含了协程协程指针任务函数这个我后面会介绍在任务队列中不单单只有协程而是有三种类型的任务。而N-M模型构成了调度器的基调想必学过生产消费者模型的同学能够很好的理解调度器的工作原理在此我不过多赘述了。和协程一样调度器也专门设立了线程的独立变量一个指向的是管理当前工作线程的调度器另一个比较关键所处线程的不同作用不一样如果在工作线程中也就是调度器管理的线程它和线程的主协程所指的都是一样的而且在主线程中是有所区别的如果主线程也参与调度协程那么线程中主协程的指针就是指向保存主线程上下文的主协程而调度器的主协程依旧指向run循环。为了不产生疑问这里特别讲或许我上章没说清线程的主协程指针起到的作用是保存整个线程的上下文只是在工作线程中线程内部一直在执行的是run循环所以才和调度器的主协程指针指向的是同一目标。但在主线程main中线程的主协程指针保存的是整个主线程的上下文在主线程参与调度协程的情况下如果指向还一样主线程就死循环了所以注意区分。那么说回调度器构造函数第一个参数是线程池内线程的数量第二个参数比较关键它决定了创建调度器的线程是否参与调度协程也就是是否参与工作那么线程内的主协程指针作用就来了在大部分情况下创建调度器的一般是主线程所以看起来多此一举的线程的主协程指针作用就体现出来了最后一个就是调度器的命名。函数的内容很简单先判断是否调动主线程如果不就给主线程id挂上-1然后创建对应数量线程如果参与就先创建主协程然后让创建run循环开始挂牌上班。这是调度器的启动程序最重要的部分就是根据线程数量直接为每个线程绑定一个run循环开始上班下面就是线程的工作函数了整个工作线程其实就是一直在跑这个函数首先先确认自身是不是主线程如果是就获取主协程指针然后创建idle协程这个后面有大用而cb_fiber就是用来包装回调函数的因为run接收三种任务类型需要把函数包装成协程ft就是任务的临时载体。进入循环后第一件事就是重置临时载体 ft然后加锁进入临界区在任务队列 m_fibers 中寻找目标。这里的逻辑非常精细体现了调度器的智能1.线程亲和性检查代码首先判断 it-thread ! -1 it-thread ! sylar::GetThreadId()。这意味着任务可能被指定了特定的执行线程。如果当前任务不是给我的我就跳过它但我会记下 tickle_me true。这就像是“这个快递不是我的但我看到后面还有快递我得喊一声让负责那个快递的同事醒醒。”2.状态合法性检查如果遇到状态为 EXEC正在执行的协程说明它可能正在被其他线程处理或者处于某种中间状态直接跳过避免并发冲突。3.提取任务一旦找到符合条件的任务就把它从队列中移除erase装入 ft并将活跃线程数 m_activeThreadCount 加一。4.协作式唤醒退出临界区后如果 tickle_me 为真说明任务队列里还有剩或者有别人的任务。此时调用 tickle()就像拍一拍其他正在 idle 的线程“别睡了起来干活了”这是高性能调度器的关键确保没有线程在有事可做时处于空闲状态。拿到任务后调度器根据任务的类型进入了三个不同的执行分支这是整个 run 函数最精彩的部分1.执行已有协程Fiber如果 ft.fiber 存在说明这是一个已经包装好的协程任务。切换调用 ft.fiber-swapIn()将 CPU 控制权交给这个子协程。回归与处理当代码执行到下一行时说明子协程已经 yield 回来了。此时需要根据它的状态做善后如果状态是 READY说明它只是暂时让出 CPU比如等待 IO那就把它重新 schedule 回队列等待下次执行。如果既没结束也没异常将其状态置为 HOLD暂停保留现场。2.执行回调函数Callback如果 ft.cb 存在说明这是一个普通的函数任务。协程调度器只能调度协程所以必须先“包装”。复用与创建这里用到了之前准备的 cb_fiber。如果它存在就 reset 一下复用如果不存在就 new 一个新的。这是一种常见的性能优化手段避免频繁创建销毁协程对象。执行与善后调用 cb_fiber-swapIn() 执行函数。执行完后同样根据状态决定是重新入队READY还是销毁TERM/EXCEPT。3.空闲状态Idle如果 ft 里既没有协程也没有回调说明任务队列空了。进入 Idle此时调度器不能闲着于是切换到 idle_fiber。忙等待与节能idle 协程通常会执行 YieldToHold让出 CPU 时间片直到有新任务唤醒它。退出机制如果 idle_fiber 的状态变成了 TERM结束说明调度器收到了停止信号整个 while(true) 循环才会 break线程结束运行。这就是用户塞任务进队的内部函数scheduleNoLock无锁任务入队函数。核心作用是判断任务队列是否为空决定是否需要唤醒线程并将单个协程或回调函数封装后加入队列。而schedule批量外部批量调度接口。通过加锁保证线程安全循环调用scheduleNoLock将一批任务默认不指定线程加入队列。关键优化在于无论批量添加多少任务最后仅调用一次tickle()唤醒工作线程避免频繁系统调用带来的性能损耗。以上就是调度器的内容了我只进行了功能上的说明还有一些函数我没有细讲只有重要的部分地方我进行了详解所以观感差的话请见谅如果对你有帮助我感到十分荣幸有什么疑问可以提出我们友好讨论谢谢大家