• apollo源码解读:/cyber/scheduler 模块


    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静态函数:删除指定协程

  • 相关阅读:
    k8s-11 网络策略
    JavaScript基础07——变量拓展-数组
    ISO质量管理体系认证有什么优势
    Mistral 7B 比Llama 2更好的开源大模型 (一)
    多路波形发生器的控制
    javaWep内置对象的使用
    .NET 表达式目录树
    初探softmax
    (附源码)SSM 汽车停车位共享APP 毕业设计 041534
    二分图 二分图最大匹配
  • 原文地址:https://blog.csdn.net/liujiayu2/article/details/125447326