![](/wp-content/uploads/2024/01/frc-cf0d92129138e2c138e143696168013a.png)
一、背景技术
系统线程是一种稀缺资源且创建一个线程开销较大,频繁地创建和销毁线程反而可能使得系统在高并发时性能急剧下降。如果无限制地创建线程,不仅会消耗系统资源,还会降低系统的稳定性,甚至造成系统崩溃。
线程池的使用能够有效提升线程的可管理性,依据系统承受能力,调整线程池中工作线程的数量,对线程进行统一的分配、调优和监控。该方式能够提高任务响应速度,当任务到达时,无需等待线程创建即可立即执行。
由于时序数据采集涉及众多设备检测点且采集数据频繁,这将导致数据库中执行任务量多、并发程度高,如何在有限系统资源下维持系统稳定则尤为重要。
就上述问题及诉求,本期我们将和大家分享 ThreadPool 模块设计。该模式能够提高系统资源的使用效率,通过重复利用已创建线程避免频繁创建和销毁线程对系统资源的消耗,可保持系统执行大量高并发任务时的性能稳定,通过 ThreadPool 模式可实现统一资源管理,简化编程接口。
二、ThreadPool 整体设计
ThreadPool 在初始化阶段创建指定数量的线程并依次放在 all_thread 中(动态数组 vector,大小为传递至它的参数,不传递参数则默认大小为 128), 将指定 数量且经过初始化的 thread_id 放入到 wait_threads (list)中。其他模块需要线程资源时,可通过 ThreadPool 接口请求获得线程资源,支持并发请求。
当 wait_threads 不为空时,为请求方提供一个 Thread 用于各项操作并返回 thread_id;当 wait_threads 中为空且线程池中线程数量没有达到上限时,尝试再次初始化部分 Thread,并将 thread_id 放入到 wait_threads (list)中以供上层调用。ThreadPool 模块负责处理 Thread 在创建与回收过程中的异常。
图 1 ThreadPool 整体设计
通过图 1 可知,在初始化过程中完成 ThreadPool 的创建与初始化。ThreadPool 在创建过程中会产生固定数量的 Thread 放入到 all_threads 数组中。ThreadPool 在初始化时需要指定最大线程数,并在 all_threads 中按照下标依次进行 Thread 的初始化。
当用户通过 ThreadPool 接口申请线程资源时,ThreadPool 模块会先检查 wait_threads 是否为空,若不为空则尝试获取一个 Thread 供给调用者使用,并返回 thread_id 给用户,同时将该 thread_id 放入到 busy_threads 中。
用户可以使用 ThreadPool 模块提供的接口同步等待操作执行结束,或者提前终止操作。用户释放 Thread 资源后,再将对应 thread_id 从 busy_threads 中取出放回到 wait_threads 中,等待后续调用。
在安全结束时,调用 ThreadPool 模块提供的接口进行关闭。首先,对 ThreadPool 状态进行标记,然后在 all_threads 循环调用 Thread 的退出方法,安全关闭 ThreadPool。
三、Thread 申请流程
图 2 Thread 申请流程用户使用
ThreadPool 接口申请 Thread 资源时,首先会检查 ThreadPool 是否已经关闭,如果已经关闭,则停止本次资源申请。如果 ThreadPool 状态正常,检查 wait_threads 中是否为空,即 ThreadPool 是否存在可用的线程。若存在就尝试获取一个 Thread,将对应 thread_id 返回给申请者;若不存在,检查当前 ThreadPool 线程数量是否超过了设定的最大值,若超过,则返回申请失败。若没有超过,则尝试继续初始化部分 Thread,将对应的 thread_id 放入到 wait_threads 中,以供用户进行申请及使用。
四、状态转移
图 3 Thread 状态转移过程
1. Thread 状态转移
ThreadPool 中的 Thread 在其整个生命周期中的状态有五种,如图 3 所示。在 ThreadPool 初始化阶段,Thread 状态均处于 INIT。ThreadPool 进行初始化,当 Thread 创建成功,状态转移至 WAIT ,否则转移至 ERROR,在 WAIT 状态下 Thread 资源空闲,可以对外提供服务。
用户调用 ThreadPool 的 applyThread(),成功获取到 Thread 后,对应的线程资源被锁定,Thread 状态转移至 RUNNING。用户可以通过 thread_id 调用 cancelThread() 向循环执行的任务发起取消指令,Thread 中循环执行的任务会通过 checkThreadStatus() 检查到取消指令后,将Thread 置为 CANCELLING,等待正在执行的任务终止并释放对应资源(或者是调用 Thread 的 joinThread()方法,等待线程结束操作并释放资源),等待任务结束后,Thread 状态转移至 WAIT。
不管 Thread 处于 WAIT 还是 RUNNING,用户调用 ThreadPool 的 stopThread() 后,正在执行循环任务的 Thread 状态都将从 RUNNING 转移至 CANCELLING,最终转移到 WAIT 后并销毁对应资源。其中 INIT、CANCELLING 状态为过渡状态。
2. ThreadPool 状态转移
ThreadPool 在其生命周期内的状态转移过程如图 4 所示,ThreadPool 对象创建完成后,其状态设定为 INIT,此时 ThreadPool 尚无法对外提供服务。在对 ThreadPool 执行完毕 InitThreadPool() 方法后(ThreadPool 对象只能被初始化一次)状态先转移成 INITIALIZAING;ThreadPool 中完成了 Thread 对象的创建,具备了对外提供服务的条件,此时状态转移成 RUNNING。
图 4 ThreadPool 状态转移过程
在对外提供服务过程中,随着越来越多的线程资源被占用,空闲的线程越来越少,当 ThreadPool 对象的 wait_threads 为空的时候,说明 ThreadPool 中暂时没有可用的 Thread。
如果此时外部申请 Thread 资源,ThreadPool 就需要继续创建 Thread 或者等待已创建的 Thread 回收,那么这个过程中 ThreadPool 会短暂地处于 BUSY 状态。
不管 ThreadPool 对象状态处于 RUNNING 还是 BUSY,当该对象的 StopThreadPool() 方法被调用时,ThreadPool 对象则需安全退出,ThreadPool 对象状态先转移至 STOPPING,等到 ThreadPool 对象停止对外提供服务,Thread 正在执行中的操作等待同步完成,ThreadPool 对象状态先转移至 STOPPED。其中, INITIALIZAING、STOPPING 状态为过渡状态。