2021SC@SDUSC
目录
1.写在前面
ROS作为一个操作系统,其职责是协调具有不同功能的node之间的通讯与合作.要想实现这个目标,最重要的方面之一就是在合理利用资源的条件下实现较高的并发性.在ROS的内核ros-core中,负责通讯的服务器正是通过将监听到的socket连接经过封装后调用线程池的相关方法来实现并发通讯的.
2.ROS线程池概述
在操作系统这门课上我们都学习了线程的概念,线程在本文中不妨理解为实现了部分数据共享,可以分组统一管理的轻量级进程.线程在操作系统上大体上有一对一、多对一和多对多三种模型,其区别在于每个用户线程与实际处理机(内核线程)的对应关系.
(三种线程模型的示意图)
一对一模型将用户线程与内核线程一一对应,其优点是可以增加程序运行速度,提高程序的并行能力,但是对于处理机资源不能很好的利用.多对一模型显然并不能加速计算,但是仍然提供了并发能力使得程序能够同时处理多个用户的请求,使得分时系统的设计目标得以实现.多对多模型则是结合了两者的优点,将处理机放入缓冲池中择机分配给用户线程,这种设计既能减少对处理机实际数量的需求,又能尽量满足每个用户线程的计算要求,使程序具有较高的并发性,是一种较为理想的选择.本文中即将讨论的线程池即是基于多对多模型的.
经过查阅资料(见JVM线程)发现,Java中的线程在Linux/Windows系统中是一对一模型,是操作系统可感知的,且一个Java线程对应一个内核线程;在Solaris中默认使用Light Weighted Process,LWP的方式通过调度器激活等策略将Java线程映射到系统线程上.因此,我们可以认为Java线程并不是多对一模型,而是一对一模型或者是仿真一对一模型.
之前已经提到,一对一模型对资源的利用程度并不是很好,因此ROS在软件层面通过编写线程池的方式将其实际使用变为多对多模式,接下来将进行详细解读.
3.ROS线程池模型
ROS线程池的类与依赖关系如图所示,接下来对各个类的结构的方法进行大体的描述.
(1)Task:Task为ThreadPool的内部接口,代表需要线程池分配线程执行的作业.也就是说,需要使用线程池中线程执行的方法都需要封装继承了Task接口的类并重写为run方法.
(2)InterruptableTask:InterruptableTask为继承了Task的ThreadPool内部接口,顾名思义,InterruptableTask为接受被打断的作业,在被打断时可以调用其中断处理方法.
(3)Poolable:Poolable为ThreadPool的内部类,是线程池容纳的对象,即基本的工作单元.Poolable维护成员shuttingDown和thread,shuttingDown负责标记是否应当停止工作,thread是用于实际执行task的对象.
(4)ThreadPool是线程池类,负责对外提供Poolable对象并对其进行统一管理,回收完成工作的Poolable对象.ThreadPool的数据成员有用于标记线程组的ThreadGroup、标记线程池最大容量的maxSize、标记当前线程池容量的num,另外还有记录待分配Poolable对象的空闲池waitingThreads列表,管理运行中Poolable对象的runningThrads列表,以及已提交但暂未分配Poolable对象的task列表.
总而言之,ROS将一对一模型映射为多对多模型的线程池运行逻辑为:
- 新建线程池对象
- 将任务封装为线程池支持的task(继承Task接口)
- 将task提交给线程池,线程池根据不同提交方式分配Poolable对象(下文称为worker)运行任务
- 在worker运行任务完成后由线程池负责管理与回收
(作业处理逻辑以及worker的生命周期示意图)