Scheduler模块,实现了一个N-M协程调度器,也就是N个线程运行M个协程,协程可以在线程之间进行切换,也可以在某个指定的线程上运行。

调度器本质上就是内部维护一个协程任务队列,然后N个线程组成的线程池各自从这个任务队列中取协程任务并运行。N最少为1,也就是至少要有一个调度线程。然而比较特殊的一点是这里调度器的调度线程不一定要新创建,因为调度器所在的线程也可以加入调度,用于运行协程任务。比如常见的,main函数所在的线程就可以进行协程调度。

讨论几种情况:

  1. 调度器的退出问题,调度器内部有一个协程任务队列,调度器调度的实质就是内部的线程池从这个任务队列拿任务并执行,那么,停止调度该怎么实现?这里可以简化处理,强制规定只有所有的任务都完成调度时,调度器才可以退出,如果有一个任务没有执行完,那调度器就不能退出。
  2. 协程执行过程中主动调用yield让出了执行权,调度器要怎么处理?半路yield的协程显然并没有执行完,一种处理方法是调度器来帮协程擦屁股,在检测到协程yield之后主动将其再次加入调度,这样这个执行到半路的协程最终还是会被调度到,但这种策略是画蛇添足的,从生活经验的角度来看,一个成熟的协程肯定要学会自我管理,既然你自己yield了,那么你就应该自己管理好自己,而不是让别人来帮你,这样才算是一个成熟的协程。对于主动yield的协程,我们的策略是,由协程自己主动将自己加入调度,而不是由调度器来主动调度。这里规定了一点,协程在执行yield前,必须以某种形式先将自己重新添加到调度任务中。如果协程不顾后果地执行yield,最后的后果就是协程将永远无法再被执行,也就是所说的逃逸状态。(sylar的处理方法比较折衷一些,sylar定义了两种yield操作,一种是yield to ready,这种yield调度器会再次将协程加入任务队列并等待调度,另一种是yield to hold,这种yield调度器不会再将协程加入任务队列,协程在yield之前必须自己先将自己加入到协程的调度队列中,否则协程就处于逃逸状态。再说一点,sylar定义的yield to ready,在框架的其他模块上一次都没用到,看到sylar也同意,一个成熟的协程要学会自我管理。)
  3. 只使用调度器所在的线程进行调度,典型的就是main函数中定义调度器并且只使用main函数线程执行调度任务。这种场景下,可以认为是main函数先攒下一波协程,然后用协程的手段进入一个while(1)循环,再依次把这些协程消耗完。每个协程在运行时也可以继续创建新的协程并加入调度。如果所有协程都调度完了,并且没有创建新的调度任务,那么下一步就是讨论idle该如何处理。
  4. idle如何处理,也就是当调度器没有协程可调度时,调度线程该怎么办。直觉上来看这里应该有一些同步手段,比如,没有调度任务时,调度线程阻塞住,比如阻塞在一个idle协程上,等待新任务加入后退出idle协程,恢复调度。然而这种方案是无法实现的,因为单线程下,同一时间只能有一个协程在执行,如果调度器阻塞在idle协程上,那么除非idle协程自行让出执行权,否则其他的协程都得不到执行,这里就造成了一个先有鸡还是先有蛋的问题:只有创建新任务idle协程才会退出,只有idle协程退出才能创建新任务。所以,对于调度器来说,处理idle的方法简单而粗暴,如果任务队列空了,那就不停地看有没有新任务,俗称忙等待,CPU爆表。这个问题在sylar框架内无解,只有一种方法可以规避掉,那就是设置autostop标志,这个标志会使得调度器在调度完所有任务后自动退出。
  • 无标签