深入理解C++线程池的实现

深入理解C++线程池的实现

在多线程编程中,线程池是一种重要的工具,用于管理和执行多个任务,有效地利用系统资源和提升程序性能。

一、线程池的了解

1. 理解线程池的基本概念与作用

线程池由任务队列和一组工作线程组成,任务队列用于存储待执行的任务,工作线程则负责从队列中取出任务并执行。这种设计能够减少线程创建和销毁的开销,提高任务调度的效率,特别是在需要处理大量短时任务的场景下尤为显著。

2. 实现一个简单的C++线程池

2.1 线程池的基本结构

一个基本的C++线程池可以包括以下几个关键组件:

  • 任务队列(Task Queue):用于存放需要执行的任务。
  • 线程管理器(Thread Manager):负责管理线程的创建、销毁和调度。
  • 工作线程(Worker Threads):执行任务的线程池成员。
2.2 初始化与销毁

正确地初始化和销毁线程池是保证其稳定运行的重要一环。在初始化阶段,需要设置线程池的大小和任务队列的容量,并创建工作线程。在销毁时,应当停止接受新任务,等待已有任务执行完毕后再释放资源。

2.3 任务调度与执行

任务的调度是线程池的核心功能。当有新任务到达时,线程池应当将任务添加到任务队列中,并唤醒等待中的工作线程来执行任务。任务执行完成后,线程池可以通知调用者或者执行回调函数。

3. 线程池的优化与性能调优

3.1 动态调整线程数量

根据任务的实际情况,动态调整线程池的大小能够更好地利用系统资源,避免因线程过多或过少造成的性能损失。

3.2 任务优先级调度

实现任务的优先级调度可以确保重要任务优先执行,提升系统的响应速度和用户体验。

3.3 锁的使用与性能优化

在多线程环境下,合理使用互斥锁和条件变量能够有效地避免竞态条件和死锁,提升线程池的并发性能。

二、线程池的实现

has
has
manages
TaskQueue
-mutex queue_mutex
-condition_variable condition
+push(function task) : void
+pop() : function
+empty() : bool
-queue> tasks
Worker
-TaskQueue& task_queue
-thread thread
-bool stop
-mutex stop_mutex
+Worker(TaskQueue& queue)
+~Worker()
-work() : void
ThreadPool
-vector workers
-TaskQueue task_queue
+ThreadPool(size_t threads)
+~ThreadPool()
+enqueue(function f) : void

1.任务队列(TaskQueue.h)

#ifndef TASKQUEUE_H
#define TASKQUEUE_H

#include <queue>                      // 标准库队列
#include <mutex>                      // 互斥锁库
#include <condition_variable>         // 条件变量库
#include <functional>                 // 函数对象库

// 任务队列类,用于存储待执行的任务
class TaskQueue {
    
    
public:
    // 将新任务添加到任务队列
    void push(std::function<void()> task) {
    
    
        {
    
    
            std::unique_lock<std::mutex> lock(queue_mutex); // 获取互斥锁
            tasks.push(std::move(task)); // 将任务添加到队列
        }
        condition.notify_one(); // 通知一个等待中的线程
    }

    // 从任务队列中取出一个任务
    std::function<void()> pop() {
    
    
        std::unique_lock<std::mutex> lock(queue_mutex); // 获取互斥锁
        // 等待条件变量,直到有任务可取
        condition.wait(lock, [this] {
    
     return !tasks.empty(); });
        auto task = std::move(tasks.front()); // 取出任务
        tasks.pop(); // 移除已取出的任务
        return task;
    }

    // 判断任务队列是否为空
    bool empty() const {
    
    
        std::unique_lock<std::mutex> lock(queue_mutex); // 获取互斥锁
        return tasks.empty(); // 返回队列是否为空
    }

private:
    std::queue<std::function<void()>> tasks; // 任务队列
    mutable std::mutex queue_mutex; // 互斥锁,保证线程安全
    std::condition_variable condition; // 条件变量,用于线程同步
};

#endif // TASKQUEUE_H

2.工作线程(Worker.h)

#ifndef WORKER_H
#define WORKER_H

#include <thread>                     // 线程库
#include "TaskQueue.h"                // 任务队列头文件

// 工作线程类,用于执行任务
class Worker {
    
    
public:
    // 构造函数,初始化工作线程
    Worker(TaskQueue& queue) : task_queue(queue), stop(false) {
    
    
        // 启动工作线程,执行 work 方法
        thread = std::thread(&Worker::work, this);
    }

    // 析构函数,停止工作线程
    ~Worker() {
    
    
        {
    
    
            std::unique_lock<std::mutex> lock(stop_mutex); // 获取互斥锁
            stop = true; // 设置停止标志
        }
        // 添加一个空任务以唤醒可能等待的线程
        task_queue.push([] {
    
    });
        if (thread.joinable()) {
    
    
            thread.join(); // 等待工作线程结束
        }
    }

private:
    // 工作线程执行的任务
    void work() {
    
    
        while (true) {
    
    
            auto task = task_queue.pop(); // 从任务队列中取出任务
            // 如果设置了停止标志并且任务队列为空,则退出循环
            if (stop && task_queue.empty()) break;
            task(); // 执行任务
        }
    }

    TaskQueue& task_queue; // 任务队列的引用
    std::thread thread; // 工作线程
    bool stop; // 停止标志
    std::mutex stop_mutex; // 互斥锁,保证线程安全
};

#endif // WORKER_H

3.线程池(ThreadPool.h)

#ifndef THREADPOOL_H
#define THREADPOOL_H

#include <vector>                     // 向量容器库
#include "TaskQueue.h"                // 任务队列头文件
#include "Worker.h"                   // 工作线程头文件

// 线程池类,用于管理任务队列和工作线程
class ThreadPool {
    
    
public:
    // 构造函数,初始化线程池并启动指定数量的工作线程
    ThreadPool(size_t threads) {
    
    
        for (size_t i = 0; i < threads; ++i) {
    
    
            workers.emplace_back(new Worker(task_queue)); // 创建并启动工作线程
        }
    }

    // 析构函数,销毁所有工作线程
    ~ThreadPool() {
    
    
        for (auto& worker : workers) {
    
    
            delete worker; // 删除工作线程
        }
    }

    // 将新任务添加到任务队列
    template<class F>
    void enqueue(F&& f) {
    
    
        task_queue.push(std::forward<F>(f)); // 将任务添加到任务队列
    }

private:
    std::vector<Worker*> workers; // 工作线程集合
    TaskQueue task_queue; // 任务队列
};

#endif // THREADPOOL_H

4.主程序(main.cpp)

重复N次
通知等待线程
通知等待线程
等待
主线程
创建ThreadPool
ThreadPool构造函数
创建并启动Worker线程
Worker::work方法
任务队列为空?
从队列中取出任务
停止线程?
停止线程
执行任务
主线程向ThreadPool添加任务
ThreadPool::enqueu
TaskQueue::push
主线程结束
ThreadPool析构函数
设置停止标志
等待所有Worker线程结束
删除Worker对象
#include <iostream>                   // 标准输入输出库
#include "ThreadPool.h"               // 线程池头文件

int main() {
    ThreadPool pool(4); // 创建一个包含四个线程的线程池

    // 向线程池中添加任务
    for (int i = 0; i < 8; ++i) {
        pool.enqueue([i] {
            // 每个任务打印任务编号和线程ID,并模拟一个耗时操作
            std::cout << "Task " << i << " executed by thread " << std::this_thread::get_id() << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟任务耗时
        });
    }

    // 确保所有任务执行完毕
    std::this_thread::sleep_for(std::chrono::seconds(10)); // 主线程等待,以确保所有任务执行完毕

    return 0; // 程序结束
}

5.详细注释说明:

  1. 任务队列(TaskQueue.h)
    • push 方法:将新任务添加到任务队列,并通知等待中的线程。
    • pop 方法:从任务队列中取出一个任务,如果队列为空,则等待新任务的到来。
    • empty 方法:判断任务队列是否为空。
  2. 工作线程(Worker.h)
    • 构造函数:启动工作线程,执行 work 方法。
    • 析构函数:设置停止标志,添加一个空任务唤醒可能等待的线程,并等待工作线程结束。
    • work 方法:从任务队列中取出任务并执行,如果设置了停止标志且任务队列为空,则退出循环。
  3. 线程池(ThreadPool.h)
    • 构造函数:创建并启动指定数量的工作线程。
    • 析构函数:销毁所有工作线程。
    • enqueue 方法:将新任务添加到任务队列。

猜你喜欢

转载自blog.csdn.net/weixin_44318762/article/details/140265076