545
社区成员
发帖
与我相关
我的任务
分享目录
线程池是一种维持并管理固定数量线程的资源池。通过复用线程来执行多个任务,避免频繁创建/销毁线程带来的开销,并提高并发任务处理效率。
生产者(Producer):提交耗时或异步任务的线程或模块。
任务队列(Task Queue):用于存放任务上下文与回调函数(通常封装为 std::function<void()> 或类似类型)。
消费者(Worker / 线程池中的线程):从任务队列取出任务并执行,负责线程调度与生命周期管理。
异步执行耗时任务,避免阻塞主线程或核心工作线程。
充分利用多核 CPU,通过并行执行提升吞吐量。
限制并发线程数量,防止系统资源耗尽。
降低线程频繁创建与销毁的开销,提高性能与稳定性。
生产者将任务提交到任务队列;线程池内部的固定数量线程不断从队列中取出任务并执行。通常配合条件变量或信号量实现任务等待与唤醒。
CPU 密集型任务(以计算为主):线程数 ≈ CPU 核心数(或略多于核心数)。
I/O 密集型任务(以等待为主):经验公式为 2 * CPU 核心数,或根据比例估算: (线程等待时间 + CPU 运算时间) * CPU 核心数 / CPU 运算时间。 选择目标:尽量保持 CPU 在高利用但不过载、避免大量上下文切换。
超过一定数量后,线程增多不会带来性能提升,反而增加上下文切换和系统开销。
固定线程数量能稳定资源使用,降低创建/销毁开销,提升性能稳定性。
实现线程池通常包括三部分:接口设计、数据结构设计、具体编码实现。
接口设计(对外 API)
构造与析构:创建指定数量线程,优雅停止并加入线程。
submit / enqueue:提交任务(支持返回值和异常传播,如使用 std::future)。
shutdown / stop:停止接受新任务并优雅退出,或强制停止。
配置接口:设置线程数量、队列容量、任务超时或拒绝策略(可选)。
数据结构设计
任务封装类型:例如 std::function<void()> 或模板化包装为可以返回值的任务。
任务队列:线程安全队列(单队列或多队列策略),配合互斥锁(std::mutex)与条件变量(std::condition_variable)。
状态标志:标识线程池运行/停止状态,便于在停止时唤醒所有等待线程。
具体编码
我们将分别实现单队列和双队列两种线程池,并对比其优缺点。
设计思路:所有工作线程共享同一个任务队列,通过互斥锁和条件变量保证线程安全。生产者向队列中 push 任务,消费者(工作线程)从队列中 pop 任务执行。

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

#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_;
};
#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();
}
}
#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_; // 非空条件变量
};
下面是一个简单的测试程序,模拟多个生产者向线程池提交任务,并统计任务完成数量。
#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;
}
本文介绍了线程池的基本概念、设计思路,并提供了两种典型的 C++ 线程池实现:单队列和双队列。单队列实现简单直观,但锁竞争可能成为性能瓶颈;双队列通过分离生产者和消费者队列,减少了锁粒度,适用于高并发场景。读者可以根据实际需求选择合适的实现,并进一步扩展支持任务返回值、超时、拒绝策略等高级特性。