cyber提供的三大功能:调度功能、通讯功能、组件功能。其中调度功能最核心的逻辑在/cyber/scheduler下面,本篇文章主要自己对该模块的研读和理解,可能会有错误,看到的朋友帮忙点播一下。
首先看下牵涉到协程的一些基本概念图谱,对协程有些基本的概念和了解
再次看下阿波罗设计的协程-调度系统类关系图谱,这个理解这个图谱会对阅读源代码有所帮助,推荐边看源代码边看图谱
里面有两大调度方案:classic(经典)和choreography(编排),图谱如下
进入正文
cyber主要提供了两种协程的调度方案classic(经典)和choreography(编排),通过配置文件对相关参数的初始化。首先看下该模块的组成成员
1.common文件夹
2.policy文件夹
3.ProcessorContext类(processor_context.h和processor_context.cc文件,为了方便以后用类来代表该两个文件内容)
4.Processor类
5.scheduler_factory类
6.Scheduler类
7.所有单元测试文件(所有测试文件不做细节分析了)
下面一一讲解上面所述的组成单元
1.common文件夹
该文件下主要提供的是一些辅助工具类,包括CvWrapper、MutexWrapper、pin_thread
CvWrapper和MutexWrapper主要是对std::condition_variable和std::mutex最简单的封装,禁用赋值构造函数,提供获取std对象的接口,包含一个std对象的成员。对std对象这么封装,说实话我是没太理解作者这么写的意图,仅仅就为了禁用赋值构造函数?要是仅仅这个原因大可不必吧,使用的时候注意点不对std对象赋值构造就可以了吧?pin_thread主要是提供了三个公共函数,具体原理在之前的文章已写过,就是调用系统api设置线程和CPU的亲和性,设置线程的调度策略和优先级
2.policy文件夹
该文件下主要提供一些多态的具体实现,包括SchedulerClassic、SchedulerChoreography、ClassicContext、ChoreographyContext。
3.ProcessorContext类
是一个抽象类,有两个多态的实现ClassicContext、ChoreographyContext,设计目的:作为Processor的私有属性的成员,提供三个虚函数接口:Shutdown、NextRoutine、Wait
Shutdown:停止执行器
NextRoutine:获取下一个协程
Wait:没活干了,阻塞执行器,等待活干
4.Processor类
执行器类,主要成员std::thread和ProcessorContext,std::thread用来创建线程去干活,ProcessorContext来切换该干哪个活了。ProcSnapshot函数提供执行器的快照,快照内容主要是协程信息
5.Scheduler类
调度器基类,有以下成员
4个虚函数:RemoveTask、DispatchTask、NotifyProcessor、RemoveCRoutine,这四个虚函数都没有默认实现,意味着此类不可以直接实例化,只能实例化其子类。RemoveTask和RemoveCRoutine都是将协程从调度队列里面移除的意思,不同的两者参数一个是名字一个是协程id,id是协程名字哈希值。移除协程的时候会将协程的force_stop_置为true,意为下次不参与调度,如果当前协程正在运行估计是停止不了。DispatchTask简而言之就是将协程加入到调度队列里面。NotifyProcessor逻辑简而言之就是调度器先切到一个协程然后调用其Notify告诉他可以接着执行了
构造函数和析构函数:空实现
静态Instance接口:空实现,实际实例化的时候使用一个在apollo::cyber::scheduler命令空间下的Instance()接口去实例化的
CreateTask函数:传入协程执行体和协程名字和DataVisitorBase对象,主要逻辑创建协程,并将其加入到任务队列(调用DispatchTask)
ProcessLevelResourceControl函数:调度器自己的线程cpu亲和性
SetInnerThreadAttr和SetInnerThreadConfs:这两个是设置特殊线程的优先级和cpu亲和性和调度策略的,以线程名字为准,比如日志线程可以利用这套函数进行设置
CheckSchedStatus:输出所有执行器当前的状态,唯一调用者/cyber/sysmo模块
Shutdown:关闭调度器,释放所有协程,停止所有执行器,清空其他资源
TaskPoolSize:获取任务池大小,在经典调度模式中该值等于执行器个数,在编排调度模式中该值一般会配置文件读取
id_cr_和id_cr_lock_:协程id的map和其锁
id_map_mutex_和cr_wl_mtx_:每个协程的锁组成的map和这个map的锁
pctxs_:context数组
processors_:执行器数组
inner_thr_confs_:非协程线程的属性配置
process_level_cpuset_:调度线程CPU亲和性
proc_num_:执行器个数
task_pool_size_:任务池个数,/cyber/task模块下有个单例TaskManager,构造的时候会创建task_pool_size_个数协程,这些协程执行体就是:从任务队列里面拿出来任务去执行
stop_:是否停止标志位
6.SchedulerChoreography类
成员变量:优先级、CPU亲和性、调度策略、CPU集合都分别有两套,一套是默认的,一套是pool系列的,另外有个cr_confs_,这个是预先定义指定task名称具备的属性(优先级和绑定到哪个执行器上面)
构造函数:根据配置文件对所有成员变量进行初始化。调用CreateProcessor创建执行器
CreateProcessor函数:根据成员属性创建两套执行器(一套默认的ChoreographyContext,一套pool系列的ClassicContext),
DispatchTask函数:创建协程对应的互斥锁,根据协程名字定义协程属性,将协程加入到协程map方便协程启动和停止和移除,将协程添加到调度队列里面从而进行调度
7.ChoreographyContext类(一个执行器对应一个ChoreographyContext)
虽然系统默认是classic的调度模式,classic模式也是简单的模式,但是代码上理解起来还是比Choreography复杂了一点,从简单的看起吧。来看下ChoreographyContext类有那些成员,有三个虚函数的实现NextRoutine、Wait、Shutdown,有三个个公开函数RemoveCRoutine、Enqueue、Notify,5个私有变量mtx_wq_、cv_wq_、notify、rq_lk_、cr_queue_,5个变量中前3个为一组,后两个为一组。下面我们一一介绍这些成员作用和目的。
前三个成员变量和成员函数Notify、Wait、Shutdown放在一起讨论:因为他们是一套逻辑,即控制调度器的资源管理,大白话讲就是,三个变量就是控制所有协程运行完毕了控制执行器的阻塞,来活了赶紧干活,notify变量其实就是个标志位,代表是否有活干,锁和条件变量就是为了控制阻塞,Notify函数逻辑就是notify加一并通知条件变量,Wait函数逻辑就是死等notify,检测到了notify等于1,将notify置为0并进行返回,Shutdown逻辑为将notify置为128并通知条件变量。
cr_queue_:一个multimap类型,key储存的是协程优先级,value储存的是协程,map按照优先级从低到高排序,这里优先级高的先执行就得到了保障,multi意为同等权限的协程会有多个
rq_lk_:控制cr_queue_的锁
RemoveCRoutine:ChoreographyContext类包含多个协程成员,要想移除其中一个就调用此函数,唯一的调用者SchedulerChoreography类的RemoveCRoutine函数。移除逻辑:在map里面找到该协程,协程停止,获得协程锁(保证其他人没有在使用中),裁判从map中移除,释放协程锁资源
Enqueue:将协程放进map里面
NextRoutine:获取下一个协程,意为上个协程已执行完毕或者进行了yield操作,执行下一个,这里值的注意的是,返回协程的时候该协程处于被锁状态,即其他方面对该协程进行Acquire会返回false
8.SchedulerClassic类
cr_confs_:从配置文件任务名->任务属性读取到信息
classic_conf_:经典调度器所有配置
构造函数:从配置文件初始化相关变量,创建执行器
CreateProcessor函数:创建执行器,值的注意的是创建执行器的个数为:分组个数*每个分组执行器个数的和,设置执行器和cpu的亲和性和调度策略
DispatchTask函数:分派任务,创建协程对应的互斥锁,将协程加入到协程map方便协程启动和停止和移除,根据协程的名字设置其优先级和分组名字,若该协程没有在配置文件里面备案过,则将其加入到第一个分组里面。根据协程的分组名字和执行权限,将其加入到对应的ClassicContext::cr_group_里面
NotifyProcessor函数:将协程设置为READY状态,调用该分组的ClassicContext::Notify
9.ClassicContext类(一个执行器对应一个ChoreographyContext)
有一说一相比ChoreographyContext来说ClassicContext更难理解,虽然经典模式是比较基础的调度模式,编排模式是比较高级点的模式。有两个成员属性wake_time和need_sleep_用不到就不再提了。有5个共有静态成员属性,为所有ClassicContext共用属性;5个私有变量属性,做下详细分析
------------------共有静态成员-------------------------
为什么采用静态属性?
答:一个context绑定一个执行器,即一个线程,静态属性意思为所有线程共用一套协程队列,即任意一个协程可能在改组定义的任何一个线程执行
cr_group_:协程组,按组名划分的一大堆协程,每个组又按照优先(20个)划分的小组,每个小组有若干个协程
rq_locks_:读写锁组,按组名划分的一大堆读写锁,每个组按照优先级(20个)划分的小组,每个小组有1个读写锁
cv_wq_:条件变量组,按组名划分的一大堆条件变量,每个组有一个条件变量
mtx_wq_:互斥锁组,按组名划分的一大堆互斥锁,每个组有一个互斥锁
notify_grp_:时间通知标识,按组名划分的一大堆标识,每个组有一个标识
------------------私有非静态成员-----------------------
multi_pri_rq_:指针,指向当前分组名称的协程组,
lq_:指针,指向当前分组名称的读写锁组,
mtx_wrapper_:指针,指向当前分组名称的互斥锁
cw_:指针,指向当前分组名称的条件变量
current_grp:当前分组名称
静态成员均为共有成员,会在SchedulerClassic和SchedulerChoreography的DispatchTask函数里面添加数据,非静态成员主要是指针指向静态成员里面当前实例的相关数据。成员函数和SchedulerChoreography类相似
InitGroup函数:初始化静态成员里面当前实例的位置,初始化非静态指针的指向
NextRoutine函数:从当前协程分组里面挨个取出协程(疑问:为什么按照权限值从大到小的顺序去取)
Wait函数:阻塞执行器
Shutdown函数:停止执行器
Notify静态函数:解除阻塞执行器
RemoveCRoutine静态函数:删除指定协程