讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下:
(02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885
文末正下方中心提供了本人联系方式,点击本人照片即可显示WX→官方认证{\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证}文末正下方中心提供了本人联系方式,点击本人照片即可显示WX→官方认证
上一篇博客中,以 DrainWorkQueue()、AddWorkItem() 为例,讲解了线程池的一个应用,或许从这个例子来看,线程池似乎很简单的,其实不然。Cartographer 线程池的相关设计是比较复杂的,其主要涉及到如下两个文件对应的类:
Task: src/cartographer/cartographer/common/task.cc
ThreadPool: src/cartographer/cartographer/common/thread_pool.cc
后续的讲解也是围绕着这两个类,不过 ThreadPool 先对来说比较简单,下面先要分析的是 Task。总的来说 Task 可是说是一个任务调配系统,或者说机制。
Cartographer 中很多任务都存在依赖关系,具体例子后续分析源码再讲解。这里列举一个比较简单的示例:比如存在任务2,记为 task23,41task2_{3,4}^{1}task23,41,其表示的含义为任务2依赖于任务3与任务4,也就是说要执行任务2必须先执行完任务3与任务4,同时task2是task1的依赖,即执行任务1之前必须先执行任务2。现在假设有三个任务 task12,3,5task1_{2,3,5}task12,3,5、task26,71task2^{1}_{6,7}task26,71、task31task3^1task31、task51task5^1task51。
task12,3,5\color{blue}{task1_{2,3,5}}task12,3,5: 该为目标任务1,其依赖任务 2,3,5,但是没有任何任务依赖该任务1。即只要执行完任务 2,3,5 即可执行该任务1。
task26,71\color{blue}{task2^{1}_{6,7}}task26,71:任务1依赖于该任务,同时该任务依赖于任务6、7。只有执行完任务6,7之后才会执行该任务,通知会通知系统执行任务1。
task31\color{blue}{task3^1}task31:该任务3不依赖于任何任务,可以直接执行,执行完之后会通知系统执行任务1。
task51\color{blue}{task5^1}task51:该任务5不依赖于任何任务,可以直接执行,执行完之后会通知系统执行任务1。
为了方便后续示例讲解,大家先熟悉上面的书写方式,后续看文章更加轻松,从上面的标识可以猜出,Cartographer 的任务之间存在依赖与被依赖的关系,可以说是错综复杂的。除此之外,对于每个任务都有状态标识,即成员变量 Task::state_。 共有如下几种状态:
enum State { NEW, DISPATCHED, DEPENDENCIES_COMPLETED, RUNNING, COMPLETED };/**NEW:新建任务, 还未schedule到线程池DISPATCHED: 任务已经schedule 到线程池DEPENDENCIES_COMPLETED: 任务依赖已经执行完成RUNNING: 任务执行中COMPLETED: 任务完成对任一个任务的状态转换顺序为:NEW->DISPATCHED->DEPENDENCIES_COMPLETED->RUNNING->COMPLETED*/
明白了 Task 的核心思想之后,再来分析其代码会简单很多,难点主要在于任务的调度上面。
首先来看看头文件,关于任务状态,即成员变量 Task::State 上面已经讲解,具体的用法后续随函数一起分析。另外还通过 std::function
// 需要执行的任务WorkItem work_item_ GUARDED_BY(mutex_);ThreadPoolInterface* thread_pool_to_notify_ GUARDED_BY(mutex_) = nullptr;// 初始状态为NEWState state_ GUARDED_BY(mutex_) = NEW;// 本任务依赖的任务的个数unsigned int uncompleted_dependencies_ GUARDED_BY(mutex_) = 0;// 依赖本任务的其他任务std::set dependent_tasks_ GUARDED_BY(mutex_);absl::Mutex mutex_;
work_item_ 后续会与对应的工作项绑定,通常是一个 lambda 表达式,即后续通过 work_item_() 形式即可进行调用。thread_pool_to_notify_ 为指向线程池的指针,在有必要的时候会通知其指向的线程池。比如 task31{task3^1}task31 任务完成时,就有可能会通知线程池执行任务1。
uncompleted_dependencies_ 记录的是本任务依赖的个数,如前面的示例 task12,3,5task1_{2,3,5}task12,3,5 表示其依赖任务数为3,分别为【任务2、任务3、任务5】。dependent_tasks_ 作用是相反,且是一个指针集合,其指向的是依赖于本任务的其他任务,如 task26,71,3task2^{1,3}_{6,7}task26,71,3 对应的 dependent_tasks_ 就包含【任务1,任务3】的指针。最后就是还有成员变量 mutex_,该就比较简单了,为一个锁,后续用到很容易理解的。
核心\color{red} 核心核心 根据上面的分析,不难猜到,其至少需要实现两个函数:Task::AddDependency()→ 为任务添加依赖任务;Task::AddDependentTask()→记录依赖于本任务的任务,即被依赖项。那么就来看看这些函数吧。