百度自动驾驶apollo源码解读7:/cyber/scheduler 模块

本文深入剖析了Apollo Cyber RT的调度系统,包括SchedulerClassic和SchedulerChoreography两种模式。经典模式下,协程池按照优先级调度,而编排模式则结合本地和全局队列,通过ChoreographyContext和ClassicContext管理。文章还介绍了如何通过配置文件调整执行器属性、协程优先级,并展示了实际测试用例,展示了不同调度模式下的行为。
摘要由CSDN通过智能技术生成

ew

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

其协程储存结构如下:

============================华丽分割线=====================================

前面我们是对cyber的调度系统咔咔的一顿分析,有一说一,比较抽象,难以理解,下面我们做一些测试例子,来进一步感受以下调度系统在做什么。

1.首先讲述以下接下来测试要用到的命令

查看进程信息
top -d 0.3 (0.3秒刷新一次)
查看线程信息
top -H -p 30852(进程id)
查看线程亲和性
cat /proc/27787(进程id)/task/27821(线程id)/status

选中字段即是亲和性的结果

查看线程的调度策略和优先级等状态信息
cat /proc/30852(进程id)/task/30886(线程id)/sched

选中字段即是调度属性和优先级属性 

2.测试工程的建立和测试方法

在/apollo/cyber目录下新建tt目录,里面包括BUILD文件和main.cc主文件。

在/apollo/conf目录下新建文件tt.conf作为启动配置文件

运行测试工程命令可以用命令

bazel run //cyber/tt:cyber_test

也可以用命令

sudo /apollo/.cache/bazel/540135163923dd7d5820f3ee4b306b32/execroot/apollo/bazel-out/k8-fastbuild/bin/cyber/tt/cyber_test

推荐使用后者,因为调用某些系统API需要sudo权限,使用bazel那一套很可能不生效

3.测试工程

3.1测试:测试两个调度器的公共部分,即设置主线程和内部线程(非协程)的属性。主要用到以下两个字段的属性

process_level_cpuset字段:主线程的CPU亲和性

threads字段:内部线程的属性

main.cc代码如下:

#include <sys/syscall.h>

#include <chrono>
#include <iostream>
#include <thread>

#include "cyber/common/global_data.h"
#include "cyber/cyber.h"
#include "cyber/scheduler/common/pin_thread.h"
#include "cyber/scheduler/policy/choreography_context.h"
#include "cyber/scheduler/processor.h"

using namespace std;
using namespace std::chrono_literals;
using namespace apollo::cyber::scheduler;
using apollo::cyber::proto::InnerThread;
using apollo::cyber::scheduler::ChoreographyContext;
using apollo::cyber::scheduler::Processor;

//参数传1的时候cpu运算需要1-2秒
int64_t cpu_run(int c) {
  int64_t sum = 0;
  for (int count = 0; count < c; count++) {
    for (int i = 0; i < 1024; i++) {
      for (int j = 0; j < 1024; j++) {
        for (int k = 0; k < 1024; k++) {
          sum += i * j * k;
        }
      }
    }
  }
  return sum;
}

int main(int argc, char* argv[]) {
  apollo::cyber::common::GlobalData::Instance()->SetProcessGroup("tt");
  apollo::cyber::Init(argv[0]);

  std::thread* pThread = new std::thread([]() {
    while (apollo::cyber::OK()) {
      auto time = apollo::cyber::Time::Now();
      std::cout << " --- " << time << std::endl;
      // std::this_thread::sleep_for(1s);
      cpu_run(1);
    }
  });
  apollo::cyber::scheduler::Instance()->SetInnerThreadAttr("test_thread",
                                                           pThread);

  cpu_run(1);
  
  apollo::cyber::WaitForShutdown();
  return 0;
}

tt.conf代码如下

scheduler_conf {
    policy: "choreography"
    process_level_cpuset: "1"  # all threads in the process are on the cpuset
    #如果processor_policy设置为SCHED_FIFO/SCHED_RR,processor_prio取值为(1-99),值越大,表明优先级越高,抢到cpu概率越大。
    #如果processor_policy设置为SCHED_OTHER,processor_prio取值为(-20-19,0为默认值),这里为nice值,nice值不影响分配到cpu的优先级,但是影响分到cpu时间片的大小,如果nice值越小,分到的时间片越多。
    #上面两句话抄写子阿波罗官方文档,是对linux的API pthread_setschedparam 的解释
    threads: [
        {
            name: "test_thread"
            cpuset: "4"
            policy: "SCHED_OTHER"
            prio: 10
        }
    ]
    
}

测试结果:cpu亲和性可以清晰的看到结果,调度策略和优先级可以用命令查看到结果,但是尚不能直接感受到区别。

3.2 经典模式和编排模式测试代码和结果

经典模式测试源码和配置文件

#include <sys/syscall.h>

#include <chrono>
#include <iostream>
#include <thread>

#include "cyber/common/global_data.h"
#include "cyber/cyber.h"
#include "cyber/scheduler/common/pin_thread.h"
#include "cyber/scheduler/policy/choreography_context.h"
#include "cyber/scheduler/processor.h"

using namespace std;
using namespace std::chrono_literals;
using namespace apollo::cyber::scheduler;
using apollo::cyber::proto::InnerThread;
using apollo::cyber::scheduler::ChoreographyContext;
using apollo::cyber::scheduler::Processor;

//参数传1的时候cpu运算需要1-2秒
int64_t cpu_run(int c) {
  int64_t sum = 0;
  for (int count = 0; count < c; count++) {
    for (int i = 0; i < 1024; i++) {
      for (int j = 0; j < 1024; j++) {
        for (int k = 0; k < 1024; k++) {
          sum += i * j * k;
        }
      }
    }
  }
  return sum;
}

int id = 1;
void fun(std::string name) {
  int runtimes = 0;
  while (apollo::cyber::OK() && runtimes < 10) {
    static std::set<std::thread::id> thread_set;
    thread_set.insert(std::this_thread::get_id());
    std::cout << name << "  "
              << id++ << "  "
              << "this_thread::get_id():" << std::this_thread::get_id()
              << "  syscall(SYS_gettid):" << syscall(SYS_gettid)
              << "  pthread_self():" << pthread_self()
              << "  thread count:" << thread_set.size();
    static std::set<apollo::cyber::croutine::CRoutine*> croutine_set;
    apollo::cyber::croutine::CRoutine* r =
        apollo::cyber::croutine::CRoutine::GetCurrentRoutine();
    if (r != nullptr) {
      croutine_set.insert(r);
      std::cout << "  croutine_id:" << r->id()
                << "  group_name:" << r->group_name() << "  GetCurrentRoutine:"
                << apollo::cyber::croutine::CRoutine::GetCurrentRoutine()
                << "  croutine name:" << r->name()
                << "  processor_id:" << r->processor_id()
                << "  croutine count: " << croutine_set.size() << std::endl;
    } else {
      std::cout << std::endl;
    }

    apollo::cyber::USleep(1* 100 * 1000);
    cpu_run(1);
    runtimes ++;
  }
}

int main(int argc, char* argv[]) {
  apollo::cyber::common::GlobalData::Instance()->SetProcessGroup("tt");
  apollo::cyber::Init(argv[0]);

  apollo::cyber::Async(fun, "A");
  apollo::cyber::Async(fun, "B");
  apollo::cyber::Async(fun, "C");

  apollo::cyber::scheduler::Instance()->CreateTask(std::bind(fun, "A"), "A");
  apollo::cyber::scheduler::Instance()->CreateTask(std::bind(fun, "B"), "B");
  apollo::cyber::scheduler::Instance()->CreateTask(std::bind(fun, "C"), "C");

  apollo::cyber::WaitForShutdown();
  return 0;
}
scheduler_conf {
    policy: "classic"
    process_level_cpuset: "0-7,16-23" # all threads in the process are on the cpuset
    threads: [
        {
            name: "async_log"
            cpuset: "1"
            policy: "SCHED_OTHER"   # policy: SCHED_OTHER,SCHED_RR,SCHED_FIFO
            prio: 0
        }, {
            name: "shm"
            cpuset: "2"
            policy: "SCHED_FIFO"
            prio: 10
        }
    ]
    classic_conf {
        groups: [
            {
                name: "group1"
                processor_num: 16
                affinity: "range"
                cpuset: "0-7"
                processor_policy: "SCHED_OTHER"  # policy: SCHED_OTHER,SCHED_RR,SCHED_FIFO
                processor_prio: 0
                tasks: [
                    {
                        name: "E"
                        prio: 0
                    }
                ]
            },{
                name: "group2"
                processor_num: 10
                affinity: "1to1"
                cpuset: "1-10"
                processor_policy: "SCHED_OTHER"
                processor_prio: 0
                tasks: [
                    {
                        name: "A"
                        prio: 1
                    },{
                        name: "B"
                        prio: 1
                    },{
                        name: "C"
                        prio: 2
                    },{
                        name: "D"
                        prio: 3
                    }
                ]
            }
        ]
    }
}

编排模式测试源码和配置文件

#include <sys/syscall.h>

#include <chrono>
#include <iostream>
#include <thread>

#include "cyber/common/global_data.h"
#include "cyber/cyber.h"
#include "cyber/scheduler/common/pin_thread.h"
#include "cyber/scheduler/policy/choreography_context.h"
#include "cyber/scheduler/processor.h"

using namespace std;
using namespace std::chrono_literals;
using namespace apollo::cyber::scheduler;
using apollo::cyber::proto::InnerThread;
using apollo::cyber::scheduler::ChoreographyContext;
using apollo::cyber::scheduler::Processor;

//参数传1的时候cpu运算需要1-2秒
int64_t cpu_run(int c) {
  int64_t sum = 0;
  for (int count = 0; count < c; count++) {
    for (int i = 0; i < 1024; i++) {
      for (int j = 0; j < 1024; j++) {
        for (int k = 0; k < 1024; k++) {
          sum += i * j * k;
        }
      }
    }
  }
  return sum;
}

int id = 1;
void fun(std::string name) {
  int runtimes = 0;
  while (apollo::cyber::OK() && runtimes < 10) {
    static std::set<std::thread::id> thread_set;
    thread_set.insert(std::this_thread::get_id());
    std::cout << name << "  "
              << id++ << "  "
              << "this_thread::get_id():" << std::this_thread::get_id()
              << "  syscall(SYS_gettid):" << syscall(SYS_gettid)
              << "  pthread_self():" << pthread_self()
              << "  thread count:" << thread_set.size();
    static std::set<apollo::cyber::croutine::CRoutine*> croutine_set;
    apollo::cyber::croutine::CRoutine* r =
        apollo::cyber::croutine::CRoutine::GetCurrentRoutine();
    if (r != nullptr) {
      croutine_set.insert(r);
      std::cout << "  croutine_id:" << r->id()
                << "  group_name:" << r->group_name() << "  GetCurrentRoutine:"
                << apollo::cyber::croutine::CRoutine::GetCurrentRoutine()
                << "  croutine name:" << r->name()
                << "  processor_id:" << r->processor_id()
                << "  croutine count: " << croutine_set.size() << std::endl;
    } else {
      std::cout << std::endl;
    }

    apollo::cyber::USleep(1* 100 * 1000);
    cpu_run(1);
    runtimes ++;
  }
}

int main(int argc, char* argv[]) {
  apollo::cyber::common::GlobalData::Instance()->SetProcessGroup("tt");
  apollo::cyber::Init(argv[0]);

  apollo::cyber::Async(fun, "A");
  apollo::cyber::Async(fun, "B");
  apollo::cyber::Async(fun, "C");

  apollo::cyber::scheduler::Instance()->CreateTask(std::bind(fun, "A"), "A");
  apollo::cyber::scheduler::Instance()->CreateTask(std::bind(fun, "B"), "B");
  apollo::cyber::scheduler::Instance()->CreateTask(std::bind(fun, "C"), "C");

  apollo::cyber::WaitForShutdown();
  return 0;
}
scheduler_conf {
    policy: "choreography"
    process_level_cpuset: "0-7,16-23"  # all threads in the process are on the cpuset
    threads: [
        {
            name: "lidar"
            cpuset: "1"
            policy: "SCHED_RR" # policy: SCHED_OTHER,SCHED_RR,SCHED_FIFO
            prio: 10
        }, {
            name: "shm"
            cpuset: "2"
            policy: "SCHED_FIFO"
            prio: 10
        }
    ]
    choreography_conf {
        choreography_processor_num: 8
        choreography_affinity: "range"
        choreography_cpuset: "2-3"
        choreography_processor_policy: "SCHED_FIFO" # policy: SCHED_OTHER,SCHED_RR,SCHED_FIFO
        choreography_processor_prio: 10

        pool_processor_num: 8
        pool_affinity: "range"
        pool_cpuset: "7-8"
        pool_processor_policy: "SCHED_OTHER"
        pool_processor_prio: 0

        tasks: [
            {
                name: "A"
                processor: 0
                prio: 1
            },
            {
                name: "B"
                processor: 0
                prio: 2
            },
            {
                name: "C"
                processor: 1
                prio: 1
            },
            {
                name: "D"
                processor: 1
                prio: 2
            },
            {
                name: "E"
            }
        ]
    }
}

Async产生的协程池里面的执行顺序
系统采用base::BoundedQueue承载所有协程(固定个数,为配置文件第一个group的processor_num),按照先后顺序执行,执行器个数代表并行个数。一个执行体执行完毕才会执行下一个执行体,即使协程进行了yeild也不会让新的执行体开始执行。执行体队列最大值为1000,超过这个值再进行Async会被抛弃。
CreateTask产生的协程的执行顺序
首先看执行体个数和执行器个数,如果执行体小于等于执行器那么大家都会得以立即执行;否则,假设执行器个数为N,那么前N个CreateTask产生的协程会得以立即执行,之后创建的就要看执行优先级了,数值越大的越会优先执行;倘若正在执行的执行体进行了yeild,会有新的执行体得到执行。

经典模式测试
1.调度系统中有个协程池,使用Async调度的任务都是由协程池去执行的,在经典模式中,协程池的属性是由第一个分组属性定义的,包括执行器的个数、CPU亲和性、优先级和调度规则,协程所在分组名称,协程名字格式/internal/task+编号,协程组里面的协程的个数和该分组的执行器个数一样。
2.使用CreateTask生成的协程会根据其名字分配到相应的分组里面,如果在所有分组都没有找到其名字,则将其加入到第一个分组里面。
3.协程优先级取值范围0-19,大于19的都将赋值为19。优先级类型为uint32意为非负整数,一旦出现配置为负数,整个classic_conf都将作废了。执行优先权将从数值大的先开始。协程池里面的由于没有名字也就没有优先权相关信息。
4.如果classic_conf没有配备分组,则会创建分组,分组名称为default_grp,执行器个数从cyber.pb.conf的default_proc_num字段读取,如果没有cyber.pb.conf字段,则执行器个数为2
5.cyber.pb.conf里面的routine_num字段,代表一次性初始化协程上下文的个数,如果将来的协程个数超越了该值之后的上下文要一个一个的申请

编排模式测试
1.协程队列在ChoreographyContext类里面的std::multimap储存着,也是按照优先级从大到小的顺序,这样可以保证优先级数值大的得到先执行的机会
2.choreography_conf里有设置调度器执行器属性的,也有设置协程池执行器属性的配置
3.配置task里多了一个processor执行器的属性,即协程可以指定专属执行器


总结
1.SchedulerClassic 采用了协程池的概念,协程没有绑定到具体的Processor,所有的协程放在全局的优先级队列中,每次从最高优先级的任务开始执行。
2.SchedulerChoreography 采用了本地队列和全局队列相结合的方式,通过"ChoreographyContext"运行本地队列的线程,通过"ClassicContext"来运行全局队列。
3.Processor对协程来说是一个逻辑上的cpu,ProcessorContext实现Processor的运行上下文,通过ProcessorContext来获取协程,休眠或者唤醒,Scheduler调度器实现了协程调度算法。


调度模式存在的共同问题
1.因为Processor每次都会从高优先级的队列开始取任务,假设Processor的数量不够,可能会出现低优先级的协程永远得不到调度的情况。
2.协程的调度没有抢占,也就是说一个协程在执行的过程中,除非主动让出,否则会一直占用Processor,如果Processor在执行低优先级的任务,来了一个高优先级的任务并不能抢占执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值