【改进版】C++ 固定线程池实现:基于调用者运行的拒绝策略优化

在高并发场景下,线程池的任务队列满负载时的拒绝策略直接影响系统稳定性。本文基于之前实现的固定线程池,优化了同步队列的Add函数逻辑,实现了更健壮的调用者运行拒绝策略,解决了任务提交阻塞、队列满时任务丢失等问题,并对核心代码进行逐行解析。

一、核心设计思路

本次优化聚焦于同步队列(SyncQueue)的Add函数,核心改进点:

  1. 引入超时等待机制,避免任务提交无限阻塞;
  2. 明确返回值语义,区分 "队列停止""队列满""任务添加成功" 三种状态;
  3. 线程池层根据返回值触发 "调用者运行" 策略,保证任务不丢失;
  4. 完善队列空停止、线程安全等细节,提升线程池鲁棒性。

二、核心代码逐部分解析

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_taskstd::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,大量任务会触发 "队列满" 状态;
  • 超出队列容量的任务会由主线程(调用者)直接执行,保证任务不丢失。

三、优化点总结

  1. 拒绝策略标准化 :通过Add函数返回值明确状态,线程池层统一处理 "调用者运行" 策略,逻辑解耦;
  2. 超时机制避免阻塞 :引入wait_for超时等待,解决生产者无限阻塞问题;
  3. 优雅停止机制WaitQueueEmptyStop保证任务执行完毕后停止,避免任务丢失;
  4. 高性能参数传递:全程使用完美转发,减少拷贝开销,支持任意类型任务;
  5. 线程安全保障:互斥锁 + 条件变量 + 原子变量,保证多线程环境下的队列 / 线程池操作安全。

四、应用场景与扩展建议

  • 适用场景:高并发、任务量波动大的场景(如后台服务、数据处理),调用者运行策略可避免任务丢失,同时控制队列负载;
  • 扩展方向
    1. 支持更多拒绝策略(如丢弃最老任务、丢弃当前任务、自定义回调);
    2. 增加线程池监控(如活跃线程数、任务执行耗时、队列长度);
    3. 实现动态线程池(根据队列负载调整工作线程数)。

本次优化在原有固定线程池基础上,强化了异常处理和任务可靠性,是对 "调用者运行" 拒绝策略的工程化落地,可直接应用于生产环境的高并发场景。

相关推荐
t***54414 分钟前
如何在Dev-C++中选择Clang编译器
开发语言·c++
橙子1991101614 分钟前
Java 基础相关
java·开发语言
汉克老师1 小时前
GESP2023年9月认证C++三级( 第一部分选择题(9-15))
c++·gesp三级·gesp3级
星越华夏1 小时前
python——三角函数用法
开发语言·python
代码中介商1 小时前
C语言数据存储深度解析:从原码反码补码到浮点数存储
c语言·开发语言·内存
2501_933329554 小时前
企业级舆情监测系统技术解析:Infoseek数字公关AI中台架构与实践
开发语言·人工智能·自然语言处理·架构
Wave8454 小时前
C++继承详解
开发语言·c++·算法
Tairitsu_H4 小时前
C++类基础概念:定义、实例化和this指针
开发语言·c++
.柒宇.4 小时前
Java八股之反射
java·开发语言
环流_4 小时前
多线程1(面试题--常见的线程创建方式)
java·开发语言·面试