在高并发场景下,线程池的任务队列满负载时的拒绝策略直接影响系统稳定性。本文基于之前实现的固定线程池,优化了同步队列的Add函数逻辑,实现了更健壮的调用者运行拒绝策略,解决了任务提交阻塞、队列满时任务丢失等问题,并对核心代码进行逐行解析。
一、核心设计思路
本次优化聚焦于同步队列(SyncQueue)的Add函数,核心改进点:
- 引入超时等待机制,避免任务提交无限阻塞;
- 明确返回值语义,区分 "队列停止""队列满""任务添加成功" 三种状态;
- 线程池层根据返回值触发 "调用者运行" 策略,保证任务不丢失;
- 完善队列空停止、线程安全等细节,提升线程池鲁棒性。
二、核心代码逐部分解析
1. 同步队列(SyncQueue_1.hpp):任务存储与线程同步核心
同步队列是线程池的任务缓冲区,负责实现线程安全的任务入队 / 出队、队列状态管理,本次核心优化集中在Add函数。
(1)成员变量定义
cpp
namespace tulun
{
static const size_t MaxTaskCount = 500;
template <class T>
class SyncQueue
{
private:
std::deque<T> m_queue; // 任务存储容器,deque兼顾头尾操作效率
std::mutex m_mutex; // 互斥锁,保证队列操作线程安全
std::condition_variable m_notEmpty; // 队列非空条件变量(消费者等待)
std::condition_variable m_notFull; // 队列非满条件变量(生产者等待)
std::condition_variable m_waitStop; // 等待队列为空的停止条件变量
int m_waitTime = 100; // 超时等待时间(100ms),避免无限阻塞
int m_maxSize; // 队列最大容量
bool m_needStop; // 队列停止标志
// ... 省略IsFull/IsEmpty工具函数
};
}
m_notEmpty/m_notFull:分别用于消费者等待任务、生产者等待队列空闲;m_waitTime:超时等待阈值,解决 "队列满时生产者无限阻塞" 问题;m_needStop:全局停止标志,控制队列生命周期。
(2)核心优化:Add 函数(任务入队逻辑)
cpp
template <class F>
int Add(F &&task)
{
std::unique_lock<std::mutex> locker(m_mutex);
// 带超时的条件等待:队列非满 或 队列停止
auto tag = m_notFull.wait_for(
locker,
std::chrono::milliseconds(m_waitTime),
[this]() -> bool { return m_needStop || !IsFull(); }
);
// 返回值语义:2-队列停止;1-队列满(超时);0-添加成功
if (m_needStop)
{
return 2; // 队列已停止,拒绝添加任务
}
if(!tag)
{
return 1; // 超时且队列仍满,触发拒绝策略
}
// 任务入队,唤醒等待的消费者
m_queue.push_back(std::forward<F>(task));
m_notEmpty.notify_all();
return 0; // 任务添加成功
}
关键改进解析:
- 替换原有的
while循环超时判断,改用wait_for + 谓词的简洁写法,逻辑更清晰; - 明确返回值:
2:队列已停止,此时无需处理任务;1:队列满且超时,触发 "调用者运行" 策略;0:任务成功入队;
- 使用
std::forward完美转发任务,支持左值 / 右值任务参数,避免拷贝开销。
(3)队列停止与等待空队列停止
cpp
void Stop()
{
{
std::unique_lock<std::mutex> locker(m_mutex);
m_needStop = true;
}
m_notEmpty.notify_all(); // 唤醒所有等待的消费者
m_notFull.notify_all(); // 唤醒所有等待的生产者
}
void WaitQueueEmptyStop()
{
std::unique_lock<std::mutex> locker(m_mutex);
// 循环等待队列为空,每次超时100ms检查一次
while (!IsEmpty())
{
m_waitStop.wait_for(locker, std::chrono::milliseconds(m_waitTime));
}
m_needStop = true;
m_notFull.notify_all();
m_notEmpty.notify_all();
}
Stop():强制停止队列,立即唤醒所有阻塞的生产者 / 消费者;WaitQueueEmptyStop():优雅停止,等待队列中所有任务执行完毕后再停止,避免任务丢失。
2. 固定线程池(FixedThreadPool.hpp/.cpp):任务调度核心
线程池基于SyncQueue实现任务提交、线程管理,核心是 "调用者运行" 拒绝策略的落地。
(1)线程池成员与构造函数
cpp
namespace tulun
{
class FixedThreadPool
{
public:
using TaskType = std::function<void(void)>; // 统一任务类型
private:
std::list<std::shared_ptr<std::thread>> m_threadgroup; // 线程组
tulun::SyncQueue<TaskType> m_queue; // 任务队列
std::atomic<bool> m_running; // 线程池运行标志
std::once_flag m_flag; // 保证Stop只执行一次
// ... 省略私有方法声明
};
// 构造函数:初始化队列容量,启动指定数量的工作线程
FixedThreadPool::FixedThreadPool(size_t m_TaskQueSize ,int numthreads)
: m_queue(m_TaskQueSize), m_running(false)
{
Start(numthreads);
}
}
TaskType:统一封装任务为无参可调用对象,兼容函数、绑定函数、lambda 等;m_threadgroup:存储工作线程智能指针,自动管理线程生命周期;std::atomic<bool> m_running:原子变量,保证线程安全的运行状态判断。
(2)工作线程运行逻辑(RunInThread)
cpp
void FixedThreadPool::RunInThread()
{
while (m_running)
{
TaskType task;
m_queue.Take(task); // 阻塞等待获取任务
if (m_running && task)
{
LOG_INFO<<"Thread task";
task(); // 执行任务
}
}
}
- 工作线程循环从队列取任务,若队列为空则阻塞在
m_queue.Take(task); - 只有线程池运行且任务有效时,才执行任务,避免空任务 / 停止后执行任务。
(3)拒绝策略落地:AddTask 与 submit
cpp
// 右值任务提交
void FixedThreadPool::AddTask(TaskType &&task)
{
if(m_queue.Put(std::forward<TaskType>(task)) != 0)
{
LOG_INFO<<"task()";
task(); // 调用者运行:队列满/停止时,由提交任务的线程执行
}
}
// 左值任务提交
void FixedThreadPool::AddTask(const TaskType &task)
{
if(m_queue.Put(task) != 0)
{
LOG_INFO<<"task()";
task(); // 调用者运行策略
}
}
// 带返回值的任务提交(通用模板)
template <class Func, class... Args>
auto submit(Func &&func, Args &&...args)
{
using RetType = decltype(func(args...));
// 封装任务为packaged_task,支持获取返回值
auto task = std::make_shared<std::packaged_task<RetType()>>(
std::bind(forward<Func>(func), forward<Args>(args)...)
);
std::future<RetType> result = task->get_future();
// 提交任务到队列,失败则调用者运行
if (m_queue.Put([task]() -> void { (*task)(); }) != 0)
{
LOG_ERROR << "Add task run task";
(*task)();
}
return result;
}
核心亮点:
AddTask:根据m_queue.Put的返回值判断是否触发拒绝策略 ------ 若返回非 0(队列满 / 停止),则由提交任务的线程直接执行任务,避免任务丢失;submit:封装带返回值的任务,通过std::packaged_task和std::future获取任务执行结果,同样支持 "调用者运行" 策略;- 完美转发参数:
std::forward保证参数传递的高效性,支持任意参数类型的任务。
(4)线程池停止逻辑
cpp
void FixedThreadPool::StopThreadGroup()
{
m_queue.WaitQueueEmptyStop(); // 等待队列空后停止
m_running = false;
// 等待所有工作线程退出
for (auto &tha : m_threadgroup)
{
tha->join();
}
}
void FixedThreadPool::Stop()
{
std::call_once(m_flag, &FixedThreadPool::StopThreadGroup, this);
}
std::call_once:保证StopThreadGroup只执行一次,避免重复停止导致的线程崩溃;WaitQueueEmptyStop:优雅停止,确保队列中所有任务执行完毕后再销毁线程池。
3. 测试代码(Test04_18_Full.cpp):验证核心功能
测试代码覆盖了基础任务提交、带返回值任务提交、高并发任务提交等场景,验证 "调用者运行" 策略的有效性:
cpp
void func(int x)
{
LOG_INFO<<"func x: "<<x;
}
int main()
{
tulun::FixedThreadPool mypool(5,1); // 队列容量5,工作线程1
const int n = 10000;
for(int i = 0;i<n;++i)
{
mypool.AddTask(std::bind(func,i)); // 提交10000个任务
}
return 0;
}
- 队列容量仅为 5,工作线程 1,大量任务会触发 "队列满" 状态;
- 超出队列容量的任务会由主线程(调用者)直接执行,保证任务不丢失。
三、优化点总结
- 拒绝策略标准化 :通过
Add函数返回值明确状态,线程池层统一处理 "调用者运行" 策略,逻辑解耦; - 超时机制避免阻塞 :引入
wait_for超时等待,解决生产者无限阻塞问题; - 优雅停止机制 :
WaitQueueEmptyStop保证任务执行完毕后停止,避免任务丢失; - 高性能参数传递:全程使用完美转发,减少拷贝开销,支持任意类型任务;
- 线程安全保障:互斥锁 + 条件变量 + 原子变量,保证多线程环境下的队列 / 线程池操作安全。
四、应用场景与扩展建议
- 适用场景:高并发、任务量波动大的场景(如后台服务、数据处理),调用者运行策略可避免任务丢失,同时控制队列负载;
- 扩展方向 :
- 支持更多拒绝策略(如丢弃最老任务、丢弃当前任务、自定义回调);
- 增加线程池监控(如活跃线程数、任务执行耗时、队列长度);
- 实现动态线程池(根据队列负载调整工作线程数)。
本次优化在原有固定线程池基础上,强化了异常处理和任务可靠性,是对 "调用者运行" 拒绝策略的工程化落地,可直接应用于生产环境的高并发场景。