线程池学习(三) 实现固定线程池(fixed_pthreadpoll)

syncqueue.hpp

cpp 复制代码
//同步队列
#ifndef SYNCQUEUE_HPP
#define SYNCQUEUE_HPP
#include<iostream>
#include<queue>
#include<list>
#include<vector>
#include<thread>
#include<mutex>
#include<functional>
#include<condition_variable>
#include <chrono>
using namespace std;
template<typename T>
class syncqueue{
private:
    list<T>m_queue;
    mutable mutex m_mutex;
    condition_variable m_notempty;//队列非空(通知取数据)
    condition_variable m_notfull;//非满(通知存数据)
    int m_maxsize;  //队列最大容量
    bool m_needstop;

    bool isfull()const{
        return m_queue.size()>=m_maxsize;
    }
    bool isempty()const{
        return m_queue.empty();
    }
    template<typename Y>
    void add(Y&& task){
        unique_lock<mutex>locker(m_mutex);
        m_notfull.wait(locker,[this]{return m_needstop||!isfull();});
        if(m_needstop)return;

        m_queue.push_back(forward<Y>(task));//完美转发插入任务
        m_notempty.notify_one();//通知等待的线程
    }
public:
    syncqueue(int maxsize=100):m_maxsize(maxsize),m_needstop(false){}
    ~syncqueue(){
        stop();
    }
    void put(const T&task){//存入任务
        add(task);
    }
    void put(T&&task){//右值版本
        add(forward<T>(task));
    }
    void take(list<T>&list){//一次性全取出
        unique_lock<mutex>locker(m_mutex);
        m_notempty.wait(locker,[this]{return m_needstop||!isempty();});
        if(m_needstop)return;
        list=move(m_queue);
        m_notfull.notify_one();
    }
    void take(T&task){//取出头部一个
        unique_lock<mutex>locker(m_mutex);
        m_notempty.wait(locker,[this]{return m_needstop||!isempty();});
        if(m_needstop)return;

        task=m_queue.front();
        m_queue.pop_front();
        m_notfull.notify_one();
    }
    void stop(){
        lock_guard<mutex>locker(m_mutex);
        m_needstop=true;
        m_notempty.notify_all();
        m_notfull.notify_all();
    }
    bool empty()const{
        lock_guard<mutex>locker(m_mutex);
        return m_queue.empty();
    }
    bool full()const{
        lock_guard<mutex>locker(m_mutex);
        return m_queue.size()>=m_maxsize;
    }
    size_t size()const{
        lock_guard<mutex>locker(m_mutex);
        return m_queue.size();
    }
    int maxsize()const{
        return m_maxsize;
    }
};

#endif

fixed_pthreadpoll

cpp 复制代码
#ifndef FIXED_THREAD_POOL_HPP
#define FIXED_THREAD_POOL_HPP
#include<iostream>
#include"syncqueue.hpp"
#include<atomic>
#include <chrono>
using namespace std;
class fixedthreadpool{
public:
    using task=function<void()>;
private:
    vector<thread>m_threads;
    syncqueue<task>m_queue;
    atomic<bool>m_running;//原子变量 无需加锁
    once_flag m_stopflag;//只允许改变一次

    void runthread(){
        while(m_running){
            task t;
            m_queue.take(t);
            if(t&&m_running)task();
        }
    }
    void stopthreads(){
        m_queue.stop();
        m_running=false;
        for(auto&thread : m_threads){
            if(thread.joinable())thread.join();
        }
        m_threads.clear();
    }
public:
    fixedthreadpool(int numthreads=thread::hardware_concurrency()):m_running(true){//当前系统硬件并发数 cpu核心数
        if(numthreads<=0)numthreads=thread::hardware_concurrency();
        for(int i=0;i<numthreads;i++){
            m_threads.emplace_back(&fixedthreadpool::runthread,this);
        }
    }
    ~fixedthreadpool(){
        stop();
    }
    void stop(){
        call_once(m_stopflag,[this]{stopthreads();});
    }
    template<typename Y>
    void addtask(Y&&task){
        m_queue.put(forward<Y>(task));
    }
    size_t threadcount()const{
        return m_threads.size();
    }

};



#endif

优化一:拒绝策略

线程池的拒绝策略

AbortPolicy :中止策略。默认的拒绝策略,直接抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。

DiscardPolicy:抛弃任务和策略。什么都不做,直接抛弃被拒绝的任务。

DiscardOldestPolicy:抛弃最老策略。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么"抛弃最旧的"策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。

CallerRunsPolicy:调用者运行策略。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。

1. 拒绝策略枚举

cpp 复制代码
// 在FixedThreadPool.h中添加
enum class RejectPolicy {
    ABORT,           // 直接抛异常(默认)
    CALLER_RUNS,     // 调用者线程执行
    DISCARD,         // 静默丢弃
    DISCARD_OLDEST   // 丢弃最老任务
};

2. 修改SyncQueue支持拒绝策略

cpp 复制代码
// 在SyncQueue.h中修改Add函数
template<typename F>
bool Add(F&& task, RejectPolicy policy = RejectPolicy::ABORT) {
    std::unique_lock<std::mutex> locker(m_mutex);
    
    // 等待队列不满
    if (!m_notFull.wait_for(locker, std::chrono::milliseconds(100), 
                           [this] { return m_needStop || !IsFull(); })) {
        // 超时,队列仍满,根据策略处理
        switch (policy) {
            case RejectPolicy::ABORT:
                throw std::runtime_error("任务队列已满");
            case RejectPolicy::DISCARD:
                return false;  // 静默丢弃
            case RejectPolicy::CALLER_RUNS:
                locker.unlock();
                task();  // 调用者线程执行
                return true;
            case RejectPolicy::DISCARD_OLDEST:
                if (!m_queue.empty()) {
                    m_queue.pop_front();  // 丢弃最老任务
                    m_queue.push_back(std::forward<F>(task));
                    m_notEmpty.notify_one();
                    return true;
                }
                // 如果队列空但还超时(不应该发生)
                return false;
            default:
                throw std::runtime_error("未知的拒绝策略");
        }
    }
    
    if (m_needStop) return false;
    
    m_queue.push_back(std::forward<F>(task));
    m_notEmpty.notify_one();
    return true;
}

3. 修改FixedThreadPool

cpp 复制代码
class FixedThreadPool {
private:
    // 添加成员变量
    RejectPolicy m_policy;
    
public:
    // 修改构造函数
    FixedThreadPool(int numThreads = std::thread::hardware_concurrency(),
                   RejectPolicy policy = RejectPolicy::ABORT)
        : m_running(true), m_policy(policy) {
        // ... 创建线程
    }
    
    // 修改AddTask
    template<typename F>
    bool AddTask(F&& task) {
        return m_queue.Put(std::forward<F>(task), m_policy);
    }
    
    // 设置拒绝策略
    void SetRejectPolicy(RejectPolicy policy) {
        m_policy = policy;
    }
};

🧪 测试拒绝策略

cpp 复制代码
void TestRejectPolicy() {
    std::cout << "=== 测试拒绝策略 ===" << std::endl;
    
    // 创建很小的线程池和队列
    FixedThreadPool pool(1, RejectPolicy::ABORT);
    
    // 快速提交大量任务
    try {
        for (int i = 0; i < 1000; i++) {
            pool.AddTask([i]() {
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            });
        }
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
    }
    
    // 测试CALLER_RUNS策略
    FixedThreadPool pool2(1, RejectPolicy::CALLER_RUNS);
    std::cout << "开始提交任务(CALLER_RUNS策略)..." << std::endl;
    
    for (int i = 0; i < 10; i++) {
        pool2.AddTask([i]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        });
    }
    
    std::this_thread::sleep_for(std::chrono::seconds(2));
}

优化二:改进addtask

改写AddTask函数降低客户端得到返回值的难度

版本1:
cpp 复制代码
template<class Func,class... Args>
auto AddTask(Func &&func, Args&&... args) ->
std::future<decltype(func(args...))>

using RetType = decltype(func(args...));
std::packaged_task<int()> task(std::bind(func, args...));
std::future<RetType> result = task.get_future();
task();

return result;
版本2:
cpp 复制代码
template<class Func,class... Args>
auto AddTask(Func &&func, Args&&... args) ->
std::future<decltype(func(args...))>

using RetType = decltype(func(args...));
std::packaged_task<RetType()> task(std::bind(std::forward<Func>(func),
std::forward<Args>(args)...));

std::future<RetType> result = task.get_future();
task();

return result;
版本3:
cpp 复制代码
template<class Func,class... Args>
auto AddTask(Func &&func, Args&&... args) ->
std::future<decltype(func(args...))>

using RetType = decltype(func(args...));
auto task = std::make_shared<std::packaged_task<RetType()> >
(std::bind(std::forward<Func>(func), std::forward<Args>(args)...));

std::future<RetType> result = task->get_future();
if (m_queue.put([task] {(*task)(); }) != 0)
{
    (*task)();
}
return result;

选最好的那个

支持任意函数和参数的AddTask

cpp 复制代码
// 在FixedThreadPool类中添加
template<typename Func, typename... Args>
auto Submit(Func&& func, Args&&... args) 
    -> std::future<decltype(func(args...))> {
    
    // 获取返回类型
    using ReturnType = decltype(func(args...));
    
    // 创建packaged_task
    auto task = std::make_shared<std::packaged_task<ReturnType()>>(
        std::bind(std::forward<Func>(func), std::forward<Args>(args)...)
    );
    
    // 获取future
    std::future<ReturnType> result = task->get_future();
    
    // 包装成void()函数放入队列
    auto wrapper = [task]() { (*task)(); };
    
    // 添加任务
    if (!AddTask(std::move(wrapper))) {
        // 如果添加失败(队列满),根据策略处理
        if (m_policy == RejectPolicy::CALLER_RUNS) {
            (*task)();  // 调用者执行
        } else {
            throw std::runtime_error("任务提交失败");
        }
    }
    
    return result;
}

使用示例

cpp 复制代码
void TestImprovedAddTask() {
    std::cout << "=== 测试改进的AddTask ===" << std::endl;
    
    FixedThreadPool pool(4);
    
    // 现在可以这样提交任务!
    auto future1 = pool.Submit([](int a, int b) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        return a + b;
    }, 10, 20);
    
    auto future2 = pool.Submit([](const std::string& s) {
        return "Hello, " + s;
    }, "World");
    
    // 获取结果
    std::cout << "结果1: " << future1.get() << std::endl;  // 30
    std::cout << "结果2: " << future2.get() << std::endl;  // Hello, World
    
    // 甚至支持成员函数
    struct Calculator {
        int multiply(int a, int b) { return a * b; }
    };
    
    Calculator calc;
    auto future3 = pool.Submit(&Calculator::multiply, &calc, 5, 6);
    std::cout << "结果3: " << future3.get() << std::endl;  // 30
}

FixedThreadPool 的使用场景

  1. 并发限制:当有大量任务需要执行,但希望限制并发线程数时,可以使用 FixedThreadPool。它可以控制并发执行的线程数量,避免系统资源过度占用和线程竞争导致性能下降。

  2. 稳定且可控的任务执行:当任务量稳定且任务的执行时间较短时,FixedThreadPool 是一个合适的选择。由于线程池中的线程数量固定,可以提供稳定的执行环境,避免频繁地创建和销毁线程的开销。

  3. 服务器应用:FixedThreadPool 适用于服务器中需要处理大量请求的场景。可以根据服务器的硬件配置和预估的负载情况,设置线程池的大小,以提供最佳的性能和资源利用率。

  4. 批量任务处理:当需要对一批任务进行并发处理时,FixedThreadPool 可以提供线程池管理和调度的支持。例如,批量数据的处理、文件的批量上传或下载等,都可以使用 FixedThreadPool 来实现并发处理。

需要注意的是,由于 FixedThreadPool 的线程数是固定的,如果任务量超过了线程池中线程的最大数量,并且任务排队队列已满,新提交的任务将会被拒绝。因此,在使用 FixedThreadPool 时,需要根据系统的负载情况、任务的特性和对任务处理的需求来合理地配置线程池的大小。

用C++11的线程相关的特性让我们编写并发程序变得简单,比如可以利用线程、条件变量、互斥量来实现一个轻巧的线程池,从而避免频繁地创建线程。使用线程池也需要注意一些问题,比如要保证线程池中的任务不能挂死,否则会耗尽线程池中的线程,造成假死现象;还要避免长时间去执行一个任务,会导致后面的任务大量堆积而得不到及时处理,对于耗时较长的任务可以考虑用单独的线程去处理。

相关推荐
豆是浪个3 小时前
Linux(Centos 7.6)命令详解:ps
linux·windows·centos
lixzest3 小时前
C++上位机软件开发入门深度学习
开发语言·c++·深度学习
于越海3 小时前
材料电子理论核心四个基本模型的python编程学习
开发语言·笔记·python·学习·学习方法
代码方舟4 小时前
Java后端实战:构建基于天远手机号码归属地核验的金融级风控模块
java·大数据·开发语言·金融
我命由我123454 小时前
开发中的英语积累 P26:Recursive、Parser、Pair、Matrix、Inset、Appropriate
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
wuk9984 小时前
基于MATLAB实现栅格地图全覆盖移动路径规划
开发语言·matlab
北岛寒沫4 小时前
北京大学国家发展研究院 经济学原理课程笔记(第二十三课 货币供应与通货膨胀)
经验分享·笔记·学习
幽络源小助理4 小时前
PHP虚拟商品自动发卡系统源码 – 支持文章付费阅读与自动发货
开发语言·php
故事不长丨4 小时前
C#集合:解锁高效数据管理的秘密武器
开发语言·windows·c#·wpf·集合·winfrom·字典
知识分享小能手4 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04中的Java与Android开发环境 (20)
java·学习·ubuntu