C++ 线程池设计

1. 设计思路与原理

线程池是一种常见的并发编程模式,旨在管理和复用多个线程以避免频繁创建和销毁线程的开销。线程池中的线程执行任务队列中的任务,而任务的调度和管理由管理组件负责。回调函数通常用于任务完成后的通知或下一步操作。以下是相关组件的设计概述:

  • 线程池(Thread Pool):维护一个固定数量的线程,负责从任务队列中取任务执行。线程池应支持任务的动态提交和任务执行状态的反馈。
  • 任务队列(Task Queue):保存待执行的任务,通常是线程安全的队列。任务可以是不同类型的操作,执行完后可以触发回调函数。
  • 工作队列(Work Queue):与任务队列相似,有时可以直接将任务放到工作队列中,由工作线程直接处理。
  • 管理组件(Management Component):管理线程池的生命周期、任务调度和回调处理等。
  • 回调函数(Callback Functions):任务执行完成后,执行回调函数以通知任务状态或执行其他后续操作。

2. 线程池工作流程

  • 线程池创建时会启动若干个工作线程。
  • 任务管理器向任务队列中添加任务。
  • 每个工作线程等待任务队列中的任务,一旦获取到任务则执行该任务。
  • 任务执行结束后,调用回调函数通知任务完成。

3. 流程图解释

3.1. 线程池任务执行流程

+-------------------------+ +--------------------+
| TaskManager (管理组件) | | ThreadPool (线程池) |
+-------------------------+ +--------------------+
| |
| |
| V
| +------------------+
| | TaskQueue (任务队列) |
| +------------------+
| |
| V
+----------------------> Task Added
|
V
+-------------------------------+
| Worker Thread (工作线程) |
+-------------------------------+
|
V
Execute Task (执行任务)
|
V
Call Callback Function (调用回调)

  • TaskManager 负责将任务添加到 TaskQueue
  • ThreadPool 中的每个 Worker Thread 持续从 TaskQueue 中获取任务并执行。
  • 执行任务后,会调用 Callback Function,例如通知主线程任务完成。
3.2. 线程池组件的设计图

+---------------------------------------------------+
| TaskManager |
| +---------------------------------------------+ |
| | TaskQueue (任务队列) | |
| | +-------------------+ +---------------+| |
| | | Add Task | | Get Task || |
| | +-------------------+ +---------------+| |
| +---------------------------------------------+ |
| |
| +---------------------------------------------+ |
| | ThreadPool (线程池) | |
| | +-------------------+ +-----------------+ | |
| | | Start Threads | | Stop Threads | | |
| | +-------------------+ +-----------------+ | |
| +---------------------------------------------+ |
+---------------------------------------------------+
| |
V V
+----------------+ +----------------+
| Worker Thread 1| | Worker Thread 2|
+----------------+ +----------------+
| |
V V
Execute Task 1 (任务1) Execute Task 2 (任务2)
| |
V V
Call Callback (回调) Call Callback (回调)

  • TaskQueue 是一个线程安全的任务存储区,线程池中的多个工作线程会从任务队列中取出任务并执行。
  • 每个 Worker Thread 都是一个独立的工作线程,持续执行队列中的任务。
  • 当一个任务完成时,它会调用指定的回调函数,通常用于通知任务管理器或调用者任务已完成。
3.3. 任务执行和回调流程

+-------------------------+
| Worker Thread |
| (线程池中的一个工作线程) |
+-------------------------+
|
V
Execute Task (任务执行)
|
V
Call Callback (回调通知)
|
V
+-------------------------+
| Callback Function |
| (回调函数,通知任务完成) |
+-------------------------+

4. 设计思想与解释

  1. 线程池的设计

    • 线程池的核心目的是复用多个线程,避免频繁创建和销毁线程带来的开销。线程池中的线程在程序运行过程中一直存在,并循环等待任务。
    • 设计线程池时,需要解决线程的同步问题,确保多个线程能够安全地从任务队列中获取任务。
  2. 任务队列的设计

    • 任务队列 采用了线程安全的设计,使用 std::mutexstd::condition_variable 来确保多个线程能够安全地访问任务队列,避免数据竞争。
    • 任务队列是生产者-消费者模型的一个典型实现,线程池中的线程是消费者,管理组件是生产者。
  3. 任务的添加与执行

    • 管理组件通过向任务队列添加任务,将工作分配给线程池。每个线程从队列中获取任务并执行。
    • 任务的执行是异步的,通过 回调函数 的方式通知主线程任务的完成状态。
  4. 回调函数的设计

    • 回调函数用于任务执行完成后,向外部传递任务结果。它为任务的异步执行提供了一个反馈机制,避免阻塞主线程。

5.设计思路扩展:

  1. 多类型任务:任务不仅限于一种类型,还可以有 I/O 任务、计算任务、定时任务等,每种任务有不同的执行方式和处理机制。
  2. 任务优先级:任务队列支持优先级调度,重要任务可以优先执行。
  3. 任务依赖和回调链:任务之间可以存在依赖,任务完成时触发其他任务或回调。
  4. 更多的线程池管理功能:支持动态调整线程数量、任务的取消等高级功能。
流程图扩展
1. 多种任务类型的处理流程

+-------------------------+
| TaskManager (管理组件) |
+-------------------------+
|
| 添加任务 (I/O, 计算, 定时等)
V
+-------------------------------+
| TaskQueue (任务队列,带优先级) |
+-------------------------------+
|
V
+-------------------------------+
| Worker Thread 1 (工作线程) |
+-------------------------------+
|
V
执行 I/O 任务 (I/O Task)
|
V
调用 I/O 回调函数

2. 任务优先级和多任务类型的队列设计

+----------------------------------+
| TaskQueue (任务队列,支持优先级) |
+----------------------------------+
| Priority: High |
| [I/O Task 1] |
| [Compute Task 3] |
+----------------------------------+
| Priority: Medium |
| [Compute Task 1] |
+----------------------------------+
| Priority: Low |
| [Scheduled Task 1] |
| [I/O Task 2] |
+----------------------------------+

任务队列中根据优先级对任务进行调度,线程池中的工作线程优先从高优先级的队列中取任务执行。

1. 设计思路

本设计的核心是实现一个线程池 ,它能够调度不同优先级的任务,并执行任务后调用相应的回调函数。整个设计分为多个组件,各司其职,组成一个灵活高效的任务处理系统。以下是详细设计思路:

1.1 主要组件
  • 任务类 (Task):封装了具体的任务函数、优先级和回调函数。
  • 任务队列 (TaskQueue):一个支持优先级调度的任务队列,使用了优先级队列存储任务。高优先级的任务会先被执行。
  • 线程池 (ThreadPool):维护了多个工作线程,线程从任务队列中提取任务执行,并调用回调函数。可以动态添加任务,并能够安全停止所有工作线程。
  • 任务管理器 (TaskManager):为用户提供接口,添加任务到线程池,并负责调度任务的执行和停止。
1.2 线程池工作流程
  1. 任务提交 :用户通过 TaskManager 将任务添加到 ThreadPool 中。
  2. 任务调度 :线程池中的每个线程从 TaskQueue 取出任务,按优先级执行。
  3. 任务执行:任务执行完成后,调用回调函数,通知任务完成。
  4. 线程停止:当所有任务处理完成,或者用户显式要求停止时,线程池会安全停止所有线程。
2. 知识要点讲解
2.1 线程池的优点
  • 资源复用:线程池中的线程在整个程序生命周期内被复用,避免频繁创建和销毁线程。
  • 任务并发处理:线程池可以同时处理多个任务,充分利用 CPU 多核能力。
  • 任务调度灵活性:通过优先级队列可以调度不同优先级的任务,确保高优先级任务被优先执行。
2.2 任务队列与调度
  • 优先级调度 :使用 std::priority_queue 数据结构,任务根据优先级被有序存储,线程从队列中提取任务时,总是优先执行高优先级任务。
  • 线程同步 :通过 std::mutexstd::condition_variable 来保护任务队列,确保多线程操作队列时的安全性。同时,条件变量 cv_ 用于通知线程有新任务可以执行。
2.3 任务与回调函数
  • 任务函数 :任务通过 std::function<void()> 封装具体的逻辑,任务类型包括 I/O、计算任务等。
  • 回调函数:回调函数用于任务完成后,执行一些附加操作或通知任务状态。
2.4 线程池的停止机制
  • 任务队列的停止:当线程池需要停止时,任务队列会唤醒所有等待的线程,通知它们可以退出。
  • 线程安全退出:工作线程会检查任务队列的停止标志,如果发现队列已经停止且无任务可执行,线程会安全退出。
3. 流程图

下面是线程池执行任务的流程图,展示了任务从添加到执行完成并回调的整个过程。

+---------------------+

| 主程序开始 |

+---------+-----------+

|

v

+---------------------+ 任务通过TaskManager添加到任务队列

| 添加任务到TaskManager|---------------------------------------->+-----------------------+

+---------------------+ | 任务队列 (TaskQueue) |

| 任务按优先级存储 |

+-----------+-----------+

|

v

+-----------------------+

| 线程池 (ThreadPool) |

| 获取任务并执行 |

+-----------+-----------+

|

+--------------------------------------------------+

|

v

+-------------------------+ 任务执行后,调用回调函数

| 执行任务 (Task::execute) |-------------------------------------->+---------------------+

+-------------------------+ | 回调函数执行完成 |

+---------------------+

|

v

+--------------------------+

| 检查是否停止 (stop) |

+--------------------------+

|

v

+-------------------------+ 继续执行下一个任务或终止线程池

| 线程安全退出或继续工作 |

+-------------------------+

|

v

+------------------+

| 主程序结束 |

+------------------+

4. 注意事项
4.1 线程池的停止

为了防止程序卡住在 getTaskwait 调用处,确保在程序结束时所有线程可以安全退出。通过 stop() 方法通知所有等待的线程,让它们退出 while 循环。

4.2 优先级队列

为了确保任务调度合理,使用 std::priority_queue 存储任务。通过自定义比较器 CompareTask,高优先级的任务会先出队列。

4.3 回调函数的作用

回调函数用于通知任务已经完成。在某些场景中,如更新 UI 或日志记录,回调函数能确保异步任务结束后执行后续逻辑。

4.4 线程同步

为了避免多个线程竞争访问任务队列,使用了互斥锁 (std::mutex) 来保护队列的并发访问。同时通过条件变量 (std::condition_variable) 进行线程间的通信,确保线程在有新任务加入时被唤醒。

6.示例代码

#include <iostream>

#include <queue>

#include <thread>

#include <mutex>

#include <condition_variable>

#include <functional>

#include <vector>

#include <future>

#include <chrono>

// 定义任务类型枚举

enum class TaskType { IO, Compute, Scheduled };

// 任务优先级枚举

enum class Priority { High, Medium, Low };

// 定义任务类,包含任务类型、优先级和回调函数

class Task {

public:

// 构造函数:传入任务类型、优先级、任务函数和回调函数

Task(TaskType type, Priority priority, std::function<void()> taskFunc, std::function<void()> callback)

: type_(type), priority_(priority), taskFunc_(taskFunc), callback_(callback) {}

// 执行任务函数和回调函数

void execute() {

taskFunc_(); // 执行任务

callback_(); // 执行回调

}

// 获取任务的优先级,用于任务调度

Priority getPriority() const {

return priority_;

}

private:

TaskType type_; // 任务类型

Priority priority_; // 任务优先级

std::function<void()> taskFunc_; // 任务函数

std::function<void()> callback_; // 回调函数

};

// 比较器用于优先级队列,优先处理高优先级任务

struct CompareTask {

// 重载操作符(),使得优先级高的任务先执行

bool operator()(const Task& t1, const Task& t2) {

return static_cast<int>(t1.getPriority()) > static_cast<int>(t2.getPriority());

}

};

// 任务队列类,支持优先级调度

class TaskQueue {

public:

// 添加任务到队列中,按优先级存储

void addTask(Task task) {

std::lock_guard<std::mutex> lock(mtx_);

tasks_.push(task);

cv_.notify_one(); // 通知等待中的线程

}

// 获取任务并从队列中删除,支持线程池终止

bool getTask(Task& task) {

std::unique_lock<std::mutex> lock(mtx_);

// 等待任务或线程池停止的条件满足

cv_.wait(lock, [this]() { return stop_ || !tasks_.empty(); });

// 如果队列为空且线程池停止,返回 false 表示没有任务可执行

if (tasks_.empty() && stop_) {

return false;

}

// 取出队列中的任务

task = tasks_.top();

tasks_.pop();

return true;

}

// 停止任务队列并唤醒所有线程

void stop() {

std::lock_guard<std::mutex> lock(mtx_);

stop_ = true;

cv_.notify_all(); // 通知所有等待中的线程,防止它们一直阻塞

}

private:

std::priority_queue<Task, std::vector<Task>, CompareTask> tasks_; // 优先级队列

std::mutex mtx_; // 保护任务队列的互斥锁

std::condition_variable cv_; // 条件变量用于任务通知

bool stop_ = false; // 停止标志

};

// 线程池类

class ThreadPool {

public:

// 构造函数:初始化线程池,并创建指定数量的工作线程

ThreadPool(size_t numThreads) : stopFlag_(false) {

for (size_t i = 0; i < numThreads; ++i) {

workers_.emplace_back([this]() {

while (true) {

Task task(TaskType::IO, Priority::Low, [] {}, [] {}); // 临时任务对象

if (!this->taskQueue_.getTask(task)) {

// 如果无法获取任务且线程池被停止,则退出

break;

}

task.execute(); // 执行任务

}

});

}

}

// 添加任务到线程池中

void enqueueTask(Task task) {

taskQueue_.addTask(task);

}

// 停止线程池

void stop() {

taskQueue_.stop(); // 停止任务队列

for (auto& worker : workers_) {

if (worker.joinable()) {

worker.join(); // 等待所有工作线程结束

}

}

}

~ThreadPool() {

stop(); // 析构时确保线程池被正确停止

}

private:

std::vector<std::thread> workers_; // 工作线程列表

TaskQueue taskQueue_; // 任务队列

bool stopFlag_; // 停止标志

};

// 回调函数类型

using Callback = std::function<void()>;

// 定义不同类型的任务函数

void ioTask(int id) {

std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟I/O操作

std::cout << "I/O Task " << id << " completed" << std::endl;

}

void computeTask(int id) {

std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟计算任务

std::cout << "Compute Task " << id << " completed" << std::endl;

}

void scheduledTask(int id) {

std::cout << "Scheduled Task " << id << " completed" << std::endl;

}

// 管理组件,负责任务添加和调度

class TaskManager {

public:

// 构造函数:初始化线程池

TaskManager(size_t numThreads) : threadPool_(numThreads) {}

// 添加 I/O 任务

void addIOTask(int id, Priority priority) {

threadPool_.enqueueTask(Task(TaskType::IO, priority, [id]() { ioTask(id); }, [id]() {

std::cout << "Callback: I/O Task " << id << " completed callback!" << std::endl;

}));

}

// 添加计算任务

void addComputeTask(int id, Priority priority) {

threadPool_.enqueueTask(Task(TaskType::Compute, priority, [id]() { computeTask(id); }, [id]() {

std::cout << "Callback: Compute Task " << id << " completed callback!" << std::endl;

}));

}

// 添加定时任务

void addScheduledTask(int id, Priority priority) {

threadPool_.enqueueTask(Task(TaskType::Scheduled, priority, [id]() { scheduledTask(id); }, [id]() {

std::cout << "Callback: Scheduled Task " << id << " completed callback!" << std::endl;

}));

}

// 停止所有任务

void stopAllTasks() {

threadPool_.stop();

std::cout << "All tasks stopped." << std::endl;

}

private:

ThreadPool threadPool_; // 线程池实例

};

// 主函数

int main() {

TaskManager manager(4); // 创建4个线程的线程池

// 添加各种类型的任务并指定优先级

manager.addIOTask(1, Priority::High);

manager.addComputeTask(2, Priority::Medium);

manager.addScheduledTask(3, Priority::Low);

manager.addIOTask(4, Priority::Medium);

manager.addComputeTask(5, Priority::High);

manager.addScheduledTask(6, Priority::Low);

// 等待任务完成

std::this_thread::sleep_for(std::chrono::seconds(5));

// 停止线程池

manager.stopAllTasks();

return 0;

}

相关推荐
数据小爬虫@14 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.16 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy21 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader28 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默39 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Code哈哈笑1 小时前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_433618441 小时前
shell 编程(二)
开发语言·bash·shell
charlie1145141911 小时前
C++ STL CookBook
开发语言·c++·stl·c++20
袁袁袁袁满1 小时前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程