一个 C++ 实现的线程池 - C++ Thread Pool

本文介绍了一个用现代C++实现的线程池库,该库已开源在GitHub。阐述了线程池在资源管理、性能优化和简化编程模型方面的优势,详细剖析了库架构、关键类与接口、实现细节,还介绍了库特性与优化、示例代码、性能测试,并对未来功能扩展提出展望。

A thread pool implemented in modern C++.

代码已开源在 GitHub

I. 引言

在现代软件开发中,多线程编程已经成为提高应用程序性能、实现并发任务处理的重要手段。然而,直接管理多个线程往往伴随着复杂性增加、资源浪费和同步问题。为了解决这些问题,线程池作为一种有效的线程管理机制应运而生。

线程池预先创建一组工作线程,将待执行任务提交到线程池,由池内线程负责任务的分配与执行,从而简化线程管理、减少系统频繁创建与销毁线程的开销、提高资源利用率。

当前现有的 C++ 线程池实现往往难以在简单易用性和文档的丰富性上取得平衡:

  • 具有良好文档说明的 C++ 线程池项目常常规模庞大、依赖复杂,难以快速理解或上手使用;
  • 实现简单的 C++ 项目往往缺乏足够的文档说明,开发者需要自行阅读源代码来理解其实现原理与使用方式。

此外对于像作者一样的以中文为母语的开发者(这一群体属实人数众多🥸)而言,我们常常感到缺乏足够的拥有中文文档的项目可供研究(甚至很多项目即使是由中国开发者开发的,它们的文档仍然是英文🥲),往往需要被迫使用英语等非母语语言进行文档的阅读,不能达到最佳效率。

针对上述问题,作者希望能够实现一个简单易用并且具有丰富中/英文文档说明的 C++ 线程池库,以期能够助力于开发者社区的学习、研究与使用。

代码已开源在 GitHub

线程池的优势

资源管理与性能优化
  • 避免频繁创建与销毁线程:线程池预先创建并维护一定数量的工作线程,避免了频繁创建和销毁线程带来的系统开销,特别是在处理大量短生命周期任务时,效果尤为显著。
  • 均衡负载与缓存局部性:线程池可以根据任务负载动态调整线程工作状态,避免过度竞争和闲置。同时,线程在执行任务过程中可以充分利用CPU缓存,提高执行效率。
  • 控制并发级别:通过限制线程池大小和任务队列容量,可以有效控制系统的并发级别,防止因过度并发导致的资源争抢和性能下降。
简化编程模型
  • 统一任务提交接口:线程池提供统一的接口供开发者提交任务,无需关心线程创建、同步等底层细节,降低了多线程编程的复杂度。
  • 异常处理与任务取消:线程池通常支持异常处理机制和任务取消功能,使得在出现异常情况或需求变更时,可以更方便地管理和调整任务执行。
    • 但本项目目前还未针对这一方面进行特别的实现🥲。

II. 设计概览

manages
thread_pool
+thread_pool(std::size_t initial_thread_count, std::size_t max_task_count)
+~thread_pool()
+template std::future submit(F &&f, Args &&...args)
+void pause()
+void resume()
+void shutdown()
+void shutdown_now()
+void terminate()
+void wait()
+void add_thread(std::size_t count_to_add)
+void remove_thread(std::size_t count_to_remove)
+void set_max_task_count(std::size_t count_to_set)
+std::size_t get_thread_count()
+std::size_t get_task_count()
worker_thread
+worker_thread(thread_pool *pool)
+~worker_thread()
+void run()
+void wake_up()
+void block()
+bool is_running()
+bool is_waiting_for_task()
+bool is_paused()
+bool is_terminating()

库架构

线程池采用了模块化设计,主要由以下几个核心组件构成:

  1. thread_pool:作为用户直接交互的接口,负责任务调度、线程管理等核心功能。用户通过创建 thread_pool 实例提交任务,控制线程池状态,并获取线程池相关信息。

    点此查看 thread_pool 类的 API 参考文档。

  2. thread_pool::worker_thread:作为线程池内部的工作单元,每个 thread_pool::worker_thread 对象代表一个独立的工作线程,负责从任务队列中取出任务并执行。

    点此查看 thread_pool::worker_thread 类的 API 参考文档。

  3. 辅助工具:包括同步原语(如互斥锁、条件变量、信号量等)以及状态管理机制,它们为线程池和工作线程之间的通信、任务同步、状态变更等操作提供了必要的支撑。

各组件间的关系如下:

  • thread_pool 类维护一个工作线程列表 std::list<worker_thread>,并通过同步原语控制任务队列的访问与状态变更。
  • thread_pool::worker_thread 类通过与 thread_pool 对象的交互,获取待执行任务、更新自身状态,并响应来自 thread_pool 的指令(如暂停、恢复、终止等)。
  • 辅助工具贯穿于整个库的设计与实现中,确保并发环境下的数据一致性与操作安全性。

关键类与接口

thread_pool

功能定位thread_pool 类是用户与线程池库交互的主要入口,封装了线程池创建、任务提交、状态控制、属性调整与信息获取等核心功能。

主要API

  • 构造函数:接受初始工作线程数和最大任务队列容量作为参数,创建并初始化线程池。

  • 任务提交:提供模板方法 submit,接受可调用对象(如函数、lambda 表达式)及参数,将其封装为任务提交至任务队列,并返回一个 std::future 对象,用于获取任务执行结果。

  • 线程池控制

    • pause:暂停线程池,阻止新任务的执行并暂停当前运行的任务。
    • resume:恢复线程池的运行,继续处理队列中的任务。
    • shutdown:设置线程池为等待任务完成状态,等待所有已提交任务执行完毕后终止线程池。
    • shutdown_now:立即终止线程池,丢弃任务队列中的未处理任务。
  • 属性调整与信息获取

    • add_thread:动态向线程池添加工作线程。
    • remove_thread:从线程池中移除指定数量的工作线程。
    • set_max_task_count:限制任务队列中允许的最大任务数量。
    • get_thread_count:查询当前线程池中活动的工作线程数。
    • get_task_count:查询等待执行的任务数量。
thread_pool::worker_thread 类(内部实现)

功能定位thread_pool::worker_thread 类是线程池内部的工作单元,负责从任务队列中取出任务并执行。其状态机模型、任务循环逻辑以及响应线程池指令的方式直接影响线程池的性能与稳定性。

内部状态与行为

  • 状态机thread_pool::worker_thread 对象具有多种状态(如运行、暂停、等待任务、将终止、已终止等),通过状态机模型管理状态变迁。
  • 任务循环:每个工作线程在一个无限循环中,根据自身状态执行相应操作,如从任务队列取任务、执行任务、响应线程池指令(如暂停、恢复、终止)等。
  • 响应指令:通过监听线程池状态变更信号和条件变量,thread_pool::worker_thread 能够及时响应来自 thread_pool 的指令,调整自身状态并执行相应操作。

III. 实现细节剖析

线程池核心机制

任务队列

数据结构选择:任务队列采用 std::queue 实现,它是一种先进先出(FIFO)的数据结构,符合线程池任务调度的基本原则——按提交顺序依次执行。

同步机制

  • 互斥锁:使用 std::shared_mutex 保护任务队列的访问,确保在多线程环境下对任务队列的操作是线程安全的。读取任务队列状态(如查询任务数量)时使用 std::shared_lock,仅需读取权限;向队列添加或移除任务时使用 std::unique_lock,需要独占访问权限。

  • 条件变量:使用 std::condition_variable_any 协调线程间的同步。当任务队列为空时,等待任务的线程会被阻塞,直到有新任务入队或线程池状态发生改变时,通过条件变量唤醒等待的线程。

工作线程管理

创建与销毁:在 thread_pool 构造函数中创建指定数量的工作线程,并将它们加入工作线程列表。线程池析构时,通过调用thread_pool::worker_threadterminate方法终止所有工作线程,并等待其退出。

状态切换

  • 线程状态:使用 std::atomic<> 存储线程状态,确保在多线程环境下状态变更的原子性。

    • std::atomic<> 是 C++ 标准库中的原子变量类型,可以保证对该类型的对象的读/写等操作是原子的。
  • 同步原语

    • 互斥锁:保护线程状态的访问,确保状态变更操作的原子性。
    • 条件变量:用于工作线程等待任务、响应线程池指令等场景,实现线程间的同步。
    • 信号量:在 thread_pool::worker_thread 类中使用 std::binary_semaphore 实现线程的暂停与恢复。
线程池状态机
初始化
暂停-pause()
恢复-resume()
shutdown()
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值