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;

}

相关推荐
hopetomorrow12 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
小牛itbull22 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i31 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
闲暇部落33 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
GIS瞧葩菜42 分钟前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
chnming19871 小时前
STL关联式容器之set
开发语言·c++
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
GIS 数据栈1 小时前
每日一书 《基于ArcGIS的Python编程秘笈》
开发语言·python·arcgis
Mr.131 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++
陌小呆^O^1 小时前
Cmakelist.txt之win-c-udp-server
c语言·开发语言·udp