引言
线程池(Thread Pool)是一种并发编程技术,通过预先创建一组线程并重复利用它们来执行任务,从而避免频繁创建和销毁线程的开销。线程池的核心思想是任务与线程解耦:任务被提交到一个共享队列中,空闲线程从队列中获取任务并执行。(给大家30秒思考一下,如果是你,你会怎样实现?)
线程池典型工作流
1. 初始化:创建固定数量的线程,并使其进入等待状态。
2. 提交任务:用户将任务添加到任务队列中。
3. 任务分配:空闲线程从队列中获取任务并执行。
4. 线程复用:任务完成后,线程返回空闲状态,等待下一个任务。
5. 终止:当线程池关闭时,所有线程安全退出。
线程池核心组件
1. 线程集合:预先创建的一组工作线程,用于执行任务。
2. 任务队列:存储待处理的任务,支持按优先级或先进先出(FIFO)等策略调度。
3. 同步机制:通过互斥锁(mutex)和条件变量(condition_variable)确保线程安全。
4. 管理逻辑:控制线程的启动、任务分配、线程回收等。
优先级任务队列
观复君想要实现带有优先级的任务队列,在每次任务插入时,容器能够自动排序,确保优先级高的队列总是第一个被执行。C++中的优先级队列容器是一个好的选择。
template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type>
> class priority_queue;
我先列出我的优先级队列code实现,下面会解释为什么这么做?
typedef std::function<void()> Task_type;
enum TaskPriority_E
{
LOW,
MIDDLE,
HIGH
};
typedef std::pair<TaskPriority_E, Task_type> TaskPair;
struct cmp
{
bool operator()(std::pair<TaskPriority_E, Task_type>& a, std::pair<TaskPriority_E, Task_type>& b)
{
return a.first < b.first;
}
};
std::priority_queue<TaskPair, std::vector<TaskPair>, cmp> _tasks;
1. 模板参数解析
-
元素类型
TaskPair
TaskPair
是std::pair<TaskPriority_E, Task_type>
的别名,其中:-
TaskPriority_E
:一个枚举类型,定义任务优先级(如LOW
、MIDDLE
、HIGH
)。 -
Task_type
:std::function<void()>
,表示一个无参数、无返回值的可调用对象(如函数、Lambda)。当然你也可以定义其他类型的function。通过将优先级与任务绑定,确保队列能按优先级调度任务。
-
-
底层容器
std::vector<TaskPair>
优先队列默认使用std::vector
作为底层存储结构,原因包括:-
连续内存分配:支持快速随机访问,堆调整操作(如
sift-up
和sift-down
)更高效。 -
动态扩容:自动管理内存,无需手动处理容量问题。
-
性能优势:相比
std::deque
,std::vector
在大多数场景下对堆操作更友好。
-
-
比较函数
cmp
cmp
是一个自定义的结构体(C++中结构体就相当于Class),通过重载operator()
定义优先级比较规则:struct cmp { bool operator()(TaskPair& a, TaskPair& b) { return a.first < b.first; // 优先级高的任务排在前面 } };
-
最大堆逻辑:若
a.first < b.first
,则a
的优先级低于b
,b
会被调整到堆顶。 -
结果:
HIGH
优先级的任务会先于MIDDLE
和LOW
被取出执行。
-
2. 优先队列的特性
-
自动排序
每次插入新任务(push
)或删除任务(pop
)时,优先队列会自动调整内部结构,确保堆顶始终是优先级最高的任务。-
插入时间复杂度:O(log n)
-
删除时间复杂度:O(log n)
-
-
操作接口
-
push(const TaskPair& task)
:将任务插入队列,触发堆调整。 -
pop()
:移除堆顶任务(优先级最高),并调整堆结构。 -
top()
:返回堆顶任务的引用(不删除)。 -
empty()
:判断队列是否为空。 -
size()
:返回队列中任务数量。
-
任务的添加与获取
-
任务添加
通过addTask
方法将任务加入队列:
也可以实现一个重载函数,用户只需传入Task_type类型的任务(默认优先级为MID)即可。void ThreadPool::addTask(const TaskPair& taskPair) { std::unique_lock<std::mutex> lock(_mutex); _tasks.emplace(taskPair); // 直接构造任务,避免拷贝 _cond.notify_one(); // 唤醒等待线程 }
void ThreadPool::addTask(const Task_type& task) { std::unique_lock<std::mutex> lock(_mutex); { TaskPair taskPair(MIDDLE, task); _tasks.emplace(taskPair); _isConditionMet = 1; _cond.notify_one(); } }
-
emplace
直接在底层容器中构造任务,减少拷贝开销。 -
添加任务后,通过条件变量
_cond
通知一个等待线程处理任务。
-
-
任务取出
通过take()
方法从队列中取出优先级最高的任务:Task_type ThreadPool::take() { std::unique_lock<std::mutex> lock(_mutex); // 等待队列非空或线程池关闭 _cond.wait(lock, [this]() { return (_isConditionMet == 1); }); if (!_tasks.empty() && _started) { Task_type task = _tasks.top().second; // 获取任务函数 _tasks.pop(); // 移除任务 return task; } return nullptr; // 若线程池关闭,返回空任务 }
-
使用
_tasks.top()
获取堆顶任务(优先级最高)。 -
_tasks.pop()
移除堆顶任务,触发堆调整。
-
线程同步与通信
1. 互斥锁(std::mutex
)
-
作用:保护共享资源(任务队列
_tasks
和状态变量)的线程安全访问。 -
关键代码段:
std::mutex _mutex; // 声明互斥锁 // 在 addTask 和 take 方法中,使用 unique_lock 加锁 void ThreadPool::addTask(const TaskPair& taskPair) { std::unique_lock<std::mutex> lock(_mutex); // 自动加锁 _tasks.emplace(taskPair); _cond.notify_one(); // 通知等待线程 } Task_type ThreadPool::take() { std::unique_lock<std::mutex> lock(_mutex); // 自动加锁 _cond.wait(lock, [this]() { return (_isConditionMet == 1); }); // ... 操作队列 }
-
解析:
-
std::unique_lock
在构造函数中自动加锁,析构时自动解锁,确保临界区代码的独占访问。 -
所有对任务队列
_tasks
的操作(如emplace
、top
、pop
)均在锁的保护下进行,避免多线程竞争。
-
2. 条件变量(std::condition_variable
)
-
作用:实现线程间的通知机制,当任务队列状态变化时唤醒等待线程。
-
关键代码段:
std::condition_variable _cond; // 声明条件变量 // 添加任务后通知等待线程 void ThreadPool::addTask(const TaskPair& taskPair) { // ... _cond.notify_one(); // 唤醒一个等待线程 } // 线程等待任务到达或线程池关闭 Task_type ThreadPool::take() { std::unique_lock<std::mutex> lock(_mutex); while (_tasks.empty() && _started) { LOG(DEBUG) << "@_@ enter wait..."; // _cond.wait(lock); _cond.wait(lock, [this]() { return (_isConditionMet == 1); }); _isConditionMet = 0; LOG(DEBUG) << "@_@ leave wait..."; } Task_type task; int size = _tasks.size(); if (!_tasks.empty() && _started) { task = _tasks.top().second; _tasks.pop(); assert((size - 1) == _tasks.size()); } return task; }
-
解析:
-
_cond.wait(lock, predicate)
:线程释放锁并进入阻塞,直到其他线程调用notify_one()
或notify_all()
,且predicate
返回true
。 -
条件谓词的作用:防止虚假唤醒(Spurious Wakeup)。即使线程被唤醒,仍需检查队列是否非空或线程池是否已关闭。
-
notify_one()
vsnotify_all()
:-
notify_one()
唤醒一个等待线程,适用于任务队列每次只添加一个任务的场景。 -
notify_all()
唤醒所有等待线程,适用于需要批量处理任务或关闭线程池的场景(例如析构函数中)。
-
-
3. 原子变量(std::atomic
)
-
作用:实现无锁的线程安全操作,用于统计和状态标记。
-
关键变量:
std::atomic<int> _num_of_valid_thread; // 当前可用线程数 std::atomic<uint8_t> _isConditionMet; // 条件是否满足的标记
-
解析:
-
_num_of_valid_thread
:-
在
threadLoop
中,线程执行任务前递减,执行完成后递增。 -
用于跟踪可用线程数,确保在析构时所有线程都能正确退出。
-
-
_isConditionMet
:-
在添加任务时设为
1
,在take()
中等待条件变量时检查该标记。 -
替换方案:代码中
_cond.wait(lock, [this]() { return (_isConditionMet == 1); })
的设计也可以通过直接检查任务队列状态(如!_tasks.empty()
)来实现。_cond.wait(lock, [this]() { return !_tasks.empty() || !_started; });
-
-
设计优势
-
优先级调度
通过自定义比较函数cmp
,实现任务按优先级处理,确保关键任务(如HIGH
)优先执行。 -
线程安全
所有队列操作(push
、pop
、top
)均在互斥锁_mutex
保护下进行,避免多线程竞争。 -
高效内存管理
std::vector
作为底层容器,结合emplace
方法,减少了任务对象的构造和拷贝开销。
完整实现
头文件
#ifndef _OTA_THREAD_POOL_HEADER_
#define _OTA_THREAD_POOL_HEADER_
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <string>
#include <utility>
#include <assert.h>
#include <vector>
#include <queue>
#include <unistd.h>
#include <atomic>
#include "ota_log.hpp"
typedef std::function<void()> Task_type;
enum TaskPriority_E
{
LOW,
MIDDLE,
HIGH
};
typedef std::pair<TaskPriority_E, Task_type> TaskPair;
struct cmp
{
bool operator()(std::pair<TaskPriority_E, Task_type>& a, std::pair<TaskPriority_E, Task_type>& b)
{
return a.first < b.first;
}
};
class ThreadPool
{
public:
ThreadPool(int size);
ThreadPool(const ThreadPool&) = delete;
const ThreadPool& operator=(const ThreadPool&) = delete;
virtual ~ThreadPool();
void start();
void threadLoop();
void addTask(const Task_type& task);
void addTask(const TaskPair& taskPair);
Task_type take();
private:
void join();
private:
int _thread_size;
std::vector<std::thread*> _threads;
std::priority_queue<TaskPair, std::vector<TaskPair>, cmp> _tasks;
std::mutex _mutex;
std::condition_variable _cond;
bool _started;
OTA::LogOTA mlog;
std::atomic<int> _num_of_valid_thread;
std::atomic<uint8_t> _isConditionMet;
};
#endif //_OTA_THREAD_POOL_HEADER_
CPP代码
#include "ota_thread_pool.h"
ThreadPool::ThreadPool(int size) : _thread_size(size), _started(false), mlog("t-pool")
{
_num_of_valid_thread = size;
_isConditionMet = 0;
start();
LOG(INFO) << "ThreadPool constructor! Thread size is " << size;
}
ThreadPool::~ThreadPool()
{
LOG(INFO) << "ThreadPool ready exit";
_started = false;
for (int i = 0; i < _thread_size; i++)
{
_isConditionMet = 1;
_cond.notify_one();
usleep(20 * 1000);
}
join();
LOG(INFO) << "ThreadPool destructor!";
}
void ThreadPool::start()
{
assert(_threads.empty());
assert(!_started);
_started = true;
_threads.reserve(_thread_size);
LOG(INFO) << "Create threadPool start";
for (int i = 0; i < _thread_size; i++)
{
_threads.push_back(new std::thread(std::bind(&ThreadPool::threadLoop, this)));
char name[32] = {0};
sprintf(name, "thread_pool_%d", i);
pthread_setname_np(_threads.at(i)->native_handle(), name);
}
LOG(INFO) << "Create threadPool end";
}
void ThreadPool::threadLoop()
{
while (_started)
{
Task_type task = take();
_num_of_valid_thread--;
if (task)
{
LOG(DEBUG) << "@_@ exec job...";
task();
LOG(DEBUG) << "@_@ job done...";
}
_num_of_valid_thread++;
}
}
void ThreadPool::addTask(const Task_type& task)
{
std::unique_lock<std::mutex> lock(_mutex);
{
LOG(DEBUG) << "@_@ num_of_valid_thread - " << _num_of_valid_thread;
TaskPair taskPair(MIDDLE, task);
_tasks.emplace(taskPair);
_isConditionMet = 1;
_cond.notify_one();
}
}
void ThreadPool::addTask(const TaskPair& taskPair)
{
std::unique_lock<std::mutex> lock(_mutex);
{
LOG(DEBUG) << "@_@ num_of_valid_thread - " << _num_of_valid_thread;
_tasks.emplace(taskPair);
_isConditionMet = 1;
_cond.notify_one();
}
}
Task_type ThreadPool::take()
{
std::unique_lock<std::mutex> lock(_mutex);
while (_tasks.empty() && _started)
{
LOG(DEBUG) << "@_@ enter wait...";
// _cond.wait(lock);
_cond.wait(lock, [this]() { return (_isConditionMet == 1); });
_isConditionMet = 0;
LOG(DEBUG) << "@_@ leave wait...";
}
Task_type task;
int size = _tasks.size();
if (!_tasks.empty() && _started)
{
task = _tasks.top().second;
_tasks.pop();
assert((size - 1) == _tasks.size());
}
return task;
}
void ThreadPool::join()
{
if (_threads.empty())
return;
for (int i = 0; i < _thread_size; i++)
{
_threads.at(i)->join();
}
}