首页 > 编程语言 >深入理解C++线程池的实现

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

时间:2024-07-08 21:57:44浏览次数:21  
标签:std task 队列 C++ queue 任务 深入 线程

深入理解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 方法:将新任务添加到任务队列。

标签:std,task,队列,C++,queue,任务,深入,线程
From: https://blog.csdn.net/weixin_44318762/article/details/140265076

相关文章

  • C++ 项目目录结构
    project_root/├──src/#源代码目录│├──main.cpp#主函数文件│├──MyClass.cpp#类的实现文件│└──...#其他源文件│├──include/#头文件目录│├──MyClass.h#类的头......
  • 将C++ DLL文件输出设置到项目调试目录
    将C++DLL文件输出设置到项目调试目录在项目开发过程中,有时需要边开发DLL代码,边开发项目,将DLL文件输出设置到解决方案的项目调试目录,调试过程中可一键生成解决方案,省去重新更换DLL文件的过程。前提:在同一解决方案下添加项目工程与DLL工程。前文提示:关于C++DLL的封装可参......
  • C++基础入门语法--代码基础框架
    文章内容概括:了解学习导入头文件、使用usingnamespacestd简化代码、创建程序基础框架、学习使用return(如需要直接复制请到文章最末尾)正文:1.学习导入头文件:    在Dev-C++编辑器中新建文件,在文件的第一行中输入:#include<iostream>    以上代码为C++导入......
  • c++ primer plus 第15章友,异常和其他:异常,15.3.5 异常规范和 C++11
    c++primerplus第15章友,异常和其他:异常,15.3.5异常规范和C++1115.3.5异常规范和C++11文章目录c++primerplus第15章友,异常和其他:异常,15.3.5异常规范和C++1115.3.5异常规范和C++1115.3.5异常规范和C++11有时候,一种理念看似有前途,但实际的使用效果并......
  • c++ primer plus 第15章友,异常和其他:15.3.1 调用abort()02
    c++primerplus第15章友,异常和其他:15.3.1调用abort()02调用abort()02文章目录c++primerplus第15章友,异常和其他:15.3.1调用abort()0215.3.1调用abort()15.3.1调用abort()对于这种问题,处理方式之一是,如果其中一个参数是另一个参数的负值,则调用abort(......
  • c++ primer plus 第15章友,异常和其他:异常,15.3.3 异常机制
    #c++primerplus第15章友,异常和其他:异常,15.3.3异常机制异常,15.3.3异常机制文章目录15.3.3异常机制15.3.3异常机制程序清单15.9error3.cpp程序清单15.10excmean.h程序清单15.11error4.cpp15.3.3异常机制15.3.3异常机制下面介绍如何使用异常机制来处......
  • 【js面试题】深入理解尾递归及其在JavaScript中的应用
    面试题:举例说明尾递归的理解,以及应用场景引言:在编程中,递归是一种常见的解决问题的方法,它允许函数调用自身来解决问题。然而,递归如果不当使用,可能会导致栈溢出错误,特别是在处理大量数据时。尾递归是一种特殊的递归形式,它能够优化递归调用,避免栈溢出的问题。本文将深入探......
  • 自动驾驶感知项目-基于多线激光雷达的小目标锥桶空间位置检测算法(ROS,C++,滤波)
    一:序言想了解更多自动驾驶项目课程以及获取学习代码的可以参考这个链接无人车采用纯跟踪算法跟随离线路径感知锥桶项目中:滤波处理是进行激光雷达目标检测的常见步骤,对原始点云数据进行预处理达到减少噪声、无效点或者数据量的效果。常用的点云滤波方法包括体素滤波、法......
  • C++数据结构底层实现算法
    1.vector底层数据结构为数组,支持快速随机访问2.list底层数据结构为双向链表,支持快速增删3.deque底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问deque是一个双端队列(double-endedqueue),也是在堆中保存内容的.每个......
  • C++ 入门02:控制结构和循环
    往期回顾:C++入门01:初识C++一、前言在上一篇文章学习中,我们了解了C++程序的基本结构、注释、数据类型、变量以及输入输出的基本用法。这一篇,我们将继续深入学习C++的控制结构和循环。二、控制结构和循环2.1、条件语句条件语句是编程中非常重要的一部分,它们允许......