【C++】线程池

C++ 线程池介绍

  1. 什么是线程池?
    线程池(Thread Pool) 是一种并发编程模型,用于管理和复用多个线程 ,避免频繁创建/销毁线程的开销。它通过预创建一组线程,并将任务提交到队列中,由空闲线程自动执行,从而提升多线程程序的性能和资源利用率。

  1. 为什么需要线程池?
  • 降低开销:线程创建/销毁成本高(涉及系统调用、内存分配)。
  • 任务调度优化:避免线程竞争CPU导致的上下文切换损耗。
  • 资源管控:限制并发线程数量,防止系统过载。
  • 简化编程:将任务提交与执行逻辑解耦。

适用场景

高并发服务器、批量数据处理、异步任务执行(如日志写入)、GUI后台计算等。

单队列线程池

简单高效,推荐使用!!!

实现

以下是基于C++11/17标准库的线程池简化实现

cpp 复制代码
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>

class ThreadPool {
public:
    explicit ThreadPool(size_t num_threads = std::thread::hardware_concurrency()) {
        for (size_t i = 0; i < num_threads; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(queue_mutex);
                        condition.wait(lock, [this] { return stop || !tasks.empty(); });
                        if (stop && tasks.empty()) return;
                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    task(); // 执行任务
                }
            });
        }
    }

    template<typename F, typename... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
        using return_type = decltype(f(args...));
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        std::future<return_type> res = task->get_future();
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
            tasks.emplace([task] { (*task)(); });
        }
        condition.notify_one(); // 唤醒一个线程
        return res;
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread& worker : workers) {
            if (worker.joinable()) worker.join();
        }
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop = false;
};

线程池核心组件

组件 作用
任务队列 存储待执行的任务(std::queue<std::function<void()>>
工作线程组 预创建的线程集合,循环从队列中取任务执行(std::vector<std::thread>
互斥锁 保护任务队列的线程安全访问(std::mutex
条件变量 协调线程的休眠与唤醒(std::condition_variable
停止标志 安全终止所有线程(bool stop

使用示例

cpp 复制代码
#include <iostream>

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

    // 提交多个任务
    std::vector<std::future<int>> results;
    for (int i = 0; i < 8; ++i) {
        results.emplace_back(pool.enqueue([i] {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            return i * i;
        }));
    }

    // 获取结果
    for (auto&& result : results) {
        std::cout << result.get() << ' ';
    }
    std::cout << std::endl;

    return 0;
}

输出0 1 4 9 16 25 36 49(执行时间约2秒,4线程并行)


优化方向

  1. 动态扩缩容:根据任务负载动态调整线程数量。
  2. 任务优先级 :支持优先级队列(如std::priority_queue)。
  3. 任务窃取:允许空闲线程从其他线程的任务队列窃取任务。
  4. 返回值处理 :使用std::futurestd::packaged_task传递异步结果。
  5. 异常处理:捕获任务中的异常并传递到主线程。

注意事项

  • 线程安全:确保任务队列的访问通过锁保护。
  • 资源释放:析构时需等待所有任务完成,避免资源泄漏。
  • 死锁风险:避免在任务中阻塞等待其他任务的结果。
  • 性能调优 :根据硬件核心数设置合理线程数(通常为CPU核心数 + 1)。

通过线程池,可以显著提升C++程序的并发性能,同时保持代码的简洁与可维护性。

双队列线程池

总体来说,不推荐!!!


线程池是否可以使用两个队列(空闲队列 & 工作队列)?

在传统线程池设计中,通常使用单个任务队列 来管理待执行的任务,所有工作线程共享此队列,并通过条件变量协调任务的获取与执行。但根据实际需求,可以通过两个队列(空闲线程队列 + 工作线程队列)来优化管理,以下是详细分析:


对比

设计方式 单任务队列 双队列(空闲+工作)
核心思想 所有线程共享一个任务队列,竞争获取任务 显式区分空闲线程和工作线程,按需调度
线程状态管理 隐式(通过条件变量等待) 显式(记录线程是否空闲)
适用场景 通用场景,简单高效 需要动态扩缩容、优先级调度等复杂场景
实现复杂度 简单 较高(需维护线程状态和队列同步)

双队列设计的意义

  • 动态线程管理
    根据任务负载动态调整活跃线程数量,例如:
    • 当任务队列积压时,从空闲队列唤醒更多线程。
    • 当任务减少时,将空闲线程挂起或释放,减少资源占用。
  • 优先级调度
    允许为不同优先级的任务分配独立队列,结合线程状态实现更精细的调度。
  • 避免过度竞争
    将空闲线程与工作线程分离,减少锁争用(例如:空闲队列单独管理)。

双队列实现示例

以下是一个基于空闲队列和工作队列的线程池框架:

cpp 复制代码
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
    explicit ThreadPool(size_t max_threads) : max_threads(max_threads) {}

    void enqueue(std::function<void()> task) {
        {
            std::unique_lock<std::mutex> lock(task_mutex);
            tasks.push(std::move(task));
        }
        // 尝试唤醒空闲线程或创建新线程
        if (!idle_threads.empty()) {
            std::unique_lock<std::mutex> lock(idle_mutex);
            auto thread = idle_threads.front();
            idle_threads.pop();
            lock.unlock();
            thread->assign_task(); // 唤醒空闲线程
        } else if (active_threads < max_threads) {
            create_thread(); // 创建新线程
        }
    }

private:
    size_t max_threads;
    size_t active_threads = 0;
    std::queue<std::function<void()>> tasks;
    std::mutex task_mutex;

    // 空闲线程队列(存储可复用的线程对象)
    std::queue<std::shared_ptr<WorkerThread>> idle_threads;
    std::mutex idle_mutex;

    class WorkerThread {
    public:
        void assign_task(std::function<void()> task) {
            {
                std::unique_lock<std::mutex> lock(mtx);
                current_task = std::move(task);
                has_task = true;
            }
            cv.notify_one();
        }

        void run() {
            while (true) {
                std::function<void()> task;
                {
                    std::unique_lock<std::mutex> lock(mtx);
                    cv.wait(lock, [&] { return has_task || terminate; });
                    if (terminate) return;
                    task = std::move(current_task);
                    has_task = false;
                }
                task(); // 执行任务
                // 任务完成后,将自己放回空闲队列
                pool->return_to_idle(this);
            }
        }

        bool is_idle() const { return !has_task; }

    private:
        std::mutex mtx;
        std::condition_variable cv;
        std::function<void()> current_task;
        bool has_task = false;
        bool terminate = false;
    };

    void create_thread() {
        auto worker = std::make_shared<WorkerThread>();
        std::thread([worker] { worker->run(); }).detach();
        active_threads++;
    }

    void return_to_idle(WorkerThread* thread) {
        std::unique_lock<std::mutex> lock(idle_mutex);
        idle_threads.push(thread);
    }
};

双队列的优缺点

优点 缺点
动态扩缩容:按需增加/减少活跃线程 实现复杂度高(需管理线程生命周期)
减少锁竞争(空闲与工作线程分离) 上下文切换可能增多(线程频繁唤醒/挂起)
支持更复杂的调度策略(如优先级) 内存占用更高(需维护额外队列)

适用场景

  • 突发性高负载:任务量波动大时,动态调整线程数量。
  • 资源敏感环境:需严格控制线程数量(如嵌入式系统)。
  • 任务优先级调度:高优先级任务优先分配空闲线程。

总结

  • 单队列足够大多数场景:简单高效,推荐优先使用。
  • 双队列适合特殊需求:当需要动态扩缩容、优先级调度或减少锁竞争时,可考虑双队列设计,但需权衡实现复杂度与性能收益。

根据具体需求选择设计,避免过度工程化!

相关推荐
whoarethenext29 分钟前
初始https附带c/c++源码使用curl库调用
服务器·c++·qt·https·curl
cloues break.1 小时前
C++进阶----多态
开发语言·c++
我不会编程5552 小时前
Python Cookbook-6.10 保留对被绑定方法的引用且支持垃圾回收
开发语言·python
道剑剑非道2 小时前
QT开发技术【qcustomplot 曲线与鼠标十字功能】
开发语言·qt·计算机外设
刘婉晴2 小时前
【环境配置】Mac电脑安装运行R语言教程 2025年
开发语言·macos·r语言
Despacito0o2 小时前
C++核心编程:类与对象全面解析
开发语言·c++
Tiger Z2 小时前
R 语言科研绘图第 43 期 --- 桑基图-冲击
开发语言·r语言·贴图
全栈师3 小时前
C#中分组循环的做法
开发语言·c#
FAREWELL000753 小时前
C#进阶学习(十六)C#中的迭代器
开发语言·学习·c#·迭代器模式·迭代器
吗喽对你问好3 小时前
Java位运算符大全
java·开发语言·位运算