c++ 线程池实现

小捏哩 2026-03-16 13:59:29

 目录

1.什么是线程池?

2.线程池的组成

3.线程池解决的问题

4.线程池如何工作(简述)

5.线程数如何选择

6.为什么维持固定数量线程?

7.手写线程池(设计思路)

8.单队列线程池实现

8.1ThreadPool.h

8.2ThreadPool.cc

8.3BlockingQueue.h

9.双队列的线程池实现

9.1ThreadPool.h

9.2ThreadPool.cc

9.3BlockingQueuepro.h

10.测试代码

11.总结


 

1.什么是线程池?

线程池是一种维持并管理固定数量线程的资源池。通过复用线程来执行多个任务,避免频繁创建/销毁线程带来的开销,并提高并发任务处理效率。

2.线程池的组成

  • 生产者(Producer):提交耗时或异步任务的线程或模块。

  • 任务队列(Task Queue):用于存放任务上下文与回调函数(通常封装为 std::function<void()> 或类似类型)。

  • 消费者(Worker / 线程池中的线程):从任务队列取出任务并执行,负责线程调度与生命周期管理。

3.线程池解决的问题

  • 异步执行耗时任务,避免阻塞主线程或核心工作线程。

  • 充分利用多核 CPU,通过并行执行提升吞吐量。

  • 限制并发线程数量,防止系统资源耗尽。

  • 降低线程频繁创建与销毁的开销,提高性能与稳定性。

4.线程池如何工作(简述)

生产者将任务提交到任务队列;线程池内部的固定数量线程不断从队列中取出任务并执行。通常配合条件变量或信号量实现任务等待与唤醒。

5.线程数如何选择

  • CPU 密集型任务(以计算为主):线程数 ≈ CPU 核心数(或略多于核心数)。

  • I/O 密集型任务(以等待为主):经验公式为 2 * CPU 核心数,或根据比例估算: (线程等待时间 + CPU 运算时间) * CPU 核心数 / CPU 运算时间。 选择目标:尽量保持 CPU 在高利用但不过载、避免大量上下文切换。

6.为什么维持固定数量线程?

  • 超过一定数量后,线程增多不会带来性能提升,反而增加上下文切换和系统开销。

  • 固定线程数量能稳定资源使用,降低创建/销毁开销,提升性能稳定性。

7.手写线程池(设计思路)

实现线程池通常包括三部分:接口设计、数据结构设计、具体编码实现。

  1. 接口设计(对外 API)

    • 构造与析构:创建指定数量线程,优雅停止并加入线程。

    • submit / enqueue:提交任务(支持返回值和异常传播,如使用 std::future)。

    • shutdown / stop:停止接受新任务并优雅退出,或强制停止。

    • 配置接口:设置线程数量、队列容量、任务超时或拒绝策略(可选)。

  2. 数据结构设计

    • 任务封装类型:例如 std::function<void()> 或模板化包装为可以返回值的任务。

    • 任务队列:线程安全队列(单队列或多队列策略),配合互斥锁(std::mutex)与条件变量(std::condition_variable)。

    • 状态标志:标识线程池运行/停止状态,便于在停止时唤醒所有等待线程。

  3. 具体编码

    我们将分别实现单队列和双队列两种线程池,并对比其优缺点。

8.单队列线程池实现

设计思路:所有工作线程共享同一个任务队列,通过互斥锁和条件变量保证线程安全。生产者向队列中 push 任务,消费者(工作线程)从队列中 pop 任务执行。

8.1ThreadPool.h

#pragma once 
​
#include <functional>
#include <vector>
#include <thread>
​
// 使用前置声明,避免包含blockingqueue.h头文件,避免依赖循环引用
template <typename T>
class BlockingQueue; // 使用前置声明只能使用指针或者引用
​
class ThreadPool
{
public:
    explicit ThreadPool(int num_thred_num); // explicit 避免隐式转换
    ~ThreadPool();
​
    void Post(std::function<void()> task);
​
private:
    void Worker();
    std::unique_ptr<BlockingQueue<std::function<void()>>> task_queue_; // unique_ptr防止拷贝构造
    std::vector<std::thread> workers_;
};

8.2ThreadPool.cc

​
#include "threadpool.h"
#include "blockingqueue.h"
​
ThreadPool::ThreadPool(int num_thred_num)
{
    task_queue_ = std::make_unique<BlockingQueue<std::function<void()>>>(); // 1. 创建任务队列
    for (size_t i = 0; i < num_thred_num; i++)
    {
        workers_.emplace_back([this]
                                { Worker(); });
    }
}
​
ThreadPool::~ThreadPool()
{
    task_queue_->Cancel(); // 1. 取消所有待处理任务
    for (auto &worker : workers_)// 2. 遍历所有工作线程
    {
        if (worker.joinable()) // 3. 检查线程是否可加入
        {
            worker.join();  // 4. 等待线程结束
        }
    }
}
​
void ThreadPool::Post(std::function<void()> task)
{
    task_queue_->Push(task);
}
​
void ThreadPool::Worker()
{
    while (true)
    {
        std::function<void()> task;
        if (!task_queue_->Pop(task))
        {
            break;
        }
        task();
    }
}

8.3BlockingQueue.h

#pragma once
​
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
​
template <typename T>
class BlockingQueue // 单生产者
{
public:
    BlockingQueue(bool non_block = false) : non_block_(non_block) {}
​
    void Push(const T &value)
    {
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(value);
        not_empty_.notify_one();
    }
​
    // 正常pop 弹出元素
    // 异常pop 取消弹出 返回false
    bool Pop(T &value)
    {
        std::unique_lock<std::mutex> lock(mutex_);
​
        // 1. mutex.unlock()
        // 2. queue_.empty() || !non_block_ 线程在wait阻塞 
        // notfiy_one 或者 notify_all 唤醒
        // 3. 假设满足条件 mutex.lock()
        // 4. 不满足条件 回到2继续阻塞
        not_empty_.wait(lock, [this]
                        { return !queue_.empty() || non_block_; });
        if (queue_.empty())
            return false;
        value = queue_.front();
        queue_.pop();
        return true;
    }
​
    void Cancel()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        non_block_ = true;
        not_empty_.notify_all();
    }
​
private:
    bool non_block_; // 是否为非阻塞模式 默认为阻塞模式
    std::queue<T> queue_;
    std::mutex mutex_;
    std::condition_variable not_empty_; // 非空条件变量
};
​

9.双队列的线程池实现

设计思路:将任务队列分为“生产者队列”和“消费者队列”,减少锁竞争。生产者只向 pro_queue_ 中 push,消费者从 cons_queue_ 中 pop。当 cons_queue_ 为空时,工作线程通过条件变量等待,并在唤醒后将 pro_queue_cons_queue_ 交换,从而批量获取任务。这种方式可以减少消费者对生产者队列的锁占用,提高并发性能。

9.1ThreadPool.h

#pragma once 
​
#include <functional>
#include <vector>
#include <thread>
​
// 使用前置声明,避免包含blockingqueue.h头文件,避免依赖循环引用
template <typename T>
class BlockingQueue; // 使用前置声明只能使用指针或者引用
​
class ThreadPool
{
public:
    explicit ThreadPool(int num_thred_num); // explicit 避免隐式转换
    ~ThreadPool();
​
    void Post(std::function<void()> task);
​
private:
    void Worker();
    std::unique_ptr<BlockingQueue<std::function<void()>>> task_queue_; // unique_ptr防止拷贝构造
    std::vector<std::thread> workers_;
};

9.2ThreadPool.cc

#include "threadpool.h"
#include "blockingqueue.h"
#include "BlockingQueuepro.h"
​
ThreadPool::ThreadPool(int num_thred_num)
{
    task_queue_ = std::make_unique<BlockingQueuepro<std::function<void()>>>(); // 1. 创建任务队列
    for (size_t i = 0; i < num_thred_num; i++)
    {
        workers_.emplace_back([this]
                                { Worker(); });
    }
}
​
ThreadPool::~ThreadPool()
{
    task_queue_->Cancel(); // 1. 取消所有待处理任务
    for (auto &worker : workers_)// 2. 遍历所有工作线程
    {
        if (worker.joinable()) // 3. 检查线程是否可加入
        {
            worker.join();  // 4. 等待线程结束
        }
    }
}
​
void ThreadPool::Post(std::function<void()> task)
{
    task_queue_->Push(task);
}
​
void ThreadPool::Worker()
{
    while (true)
    {
        std::function<void()> task;
        if (!task_queue_->Pop(task))
        {
            break;
        }
        task();
    }
}
​

9.3BlockingQueuepro.h

#pragma once
​
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
​
template <typename T>
class BlockingQueuepro {
public:
    BlockingQueuepro(bool non_block = false) : non_block_(non_block) {}
​
    void Push(const T &value) {
        std::lock_guard<std::mutex> lock(pro_mutex_);
        pro_queue_.push(value);
        not_empty_.notify_one(); // 通知一个消费者线程
    }
​
    bool Pop(T &value) {
        std::unique_lock<std::mutex> lock(cons_mutex_);
        if(cons_queue_.empty() && SwapQueue_() == 0) {
            return false;
        }
        value = cons_queue_.front();
        cons_queue_.pop();
        return true;
    }
​
    void Cancel() {
        std::lock_guard<std::mutex> lock(pro_mutex_);
        non_block_ = true;
        not_empty_.notify_all();
    }
​
private:
​
    int SwapQueue_() {
        std::unique_lock<std::mutex> lock(pro_mutex_);
        not_empty_.wait(lock, [this] { return !pro_queue_.empty() || non_block_;});
        std::swap(pro_queue_, cons_queue_);
        return cons_queue_.size();
    }
    bool non_block_; // 是否为非阻塞模式 默认为阻塞模式
    std::queue<T> pro_queue_; // 生产者队列
    std::queue<T> cons_queue_; // 消费者队列
    std::mutex pro_mutex_; // 生产者互斥锁
    std::mutex cons_mutex_; // 消费者互斥锁
    std::condition_variable not_empty_; // 非空条件变量
};

10.测试代码

下面是一个简单的测试程序,模拟多个生产者向线程池提交任务,并统计任务完成数量。

#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>
​
#include "threadpool.h"
​
// 全局计数。用于统计任务完成的数量
std::atomic<int> task_counter{0};
​
// 任务函数
void Task(int id) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "Task " << id << " completed by thread " 
              << std::this_thread::get_id() << std::endl;
    task_counter++; // 任务完成,计数加一
}
​
// 生产者线程函数
void Producer(ThreadPool &pool, int produer_id, int num_tasks) {
    for (int i = 0; i < num_tasks; i++) {
        int task_id = produer_id * 1000 + i; // 生成唯一任务ID
        pool.Post([task_id]() { Task(task_id); }); // 提交任务到线程池
        std::cout << "Producer " << produer_id 
                  << " posted Task " << task_id << std::endl;
    }
}
​
int main() {
    const int num_producers = 4; // 生产者线程数量
    const int num_tasks_per_producer = 10; // 每个生产者提交的任务数量
    const int num_threads_in_pool = 2; // 线程池中的线程数量
​
    ThreadPool pool(num_threads_in_pool); // 创建线程池
​
    std::vector<std::thread> producers; // 生产者线程容器
​
    // 启动生产者线程
    for (int i = 0; i < num_producers; i++) {
        // emplace_back直接在vector中构造对象
        producers.emplace_back(Producer, std::ref(pool), i, num_tasks_per_producer); // 启动生产者线程
        // 相当于:
        // producers.push_back(std::thread(producer, std::ref(pool), i, num_tasks_per_producer));
    }
​
    // 等待所有生产者线程完成
    for (auto &producer : producers) {
        producer.join();
    }
​
    // 等待所有任务完成
    while (task_counter < num_producers * num_tasks_per_producer) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
​
    std::cout << "All tasks completed. Total tasks: " 
              << task_counter << std::endl;
​
    return 0;
}

11.总结

本文介绍了线程池的基本概念、设计思路,并提供了两种典型的 C++ 线程池实现:单队列和双队列单队列实现简单直观,但锁竞争可能成为性能瓶颈;双队列通过分离生产者和消费者队列,减少了锁粒度,适用于高并发场景。读者可以根据实际需求选择合适的实现,并进一步扩展支持任务返回值、超时、拒绝策略等高级特性。

c++线程池实现-CSDN博客

https://github.com/0voice

...全文
24 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

545

社区成员

发帖
与我相关
我的任务
社区描述
零声学院,目前拥有上千名C/C++开发者,我们致力将我们的学员组织起来,打造一个开发者学习交流技术的社区圈子。
nginx中间件后端 企业社区
社区管理员
  • Linux技术狂
  • Yttsam
  • 零声教育-晚晚
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

请新加入的VIP学员,先将自己参加活动的【所有文章】,同步至社区:

【内容管理】-【同步至社区-【零声开发者社区】

试试用AI创作助手写篇文章吧