实现一个异步操作线程池

使用C++11实现一个异步操作线程池

基础知识

std::future
  • 介绍 : std::future 是 C++11 标准库中的一个模板类,它表示一个异步操作的结果。当我们在
    多线程编程中使用异步任务时,std::future 可以帮助我们在需要的时候获取任务的执行
    结果。需包含头文件
  • 应用场景:
异步任务:当我们需要再后台执行一些操作时,std::future 可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提高程序的执行效率。
并行控制:在多线程编程中,我们可能需要等待某些任务完成后才能继续执行其他操作。通过使用 std::future,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执行后续操作。
结果获取:std::future 提供了一种安全的方式来获取异步任务的结果。我们可以使用future::get()函数来获取任务的结果,此函数会阻塞当前线程,直到异步操作完成。
std::async
  • 介绍: std::async 是一种将任务与 std::future 关联的简单方法。它创建并运行一个异步任务,并返回一个与该任务结果关联的 std::future 对象。默认情况下,std::async 是否启动一个新线程,或者在等待 future 时,任务是否同步运行都取决于你给的参数。这个参数为 std::launch 类型:
launch::deferred:表明该函数会被延迟调用, 在执行get获取异步结果的时候,才会执行异步任务
launch::async:表明函数内部会创建线程执行异步任务,执行完任务后才会继续执行下面的代码,否则一直在get阻塞!
launch::deferred | launch::async:内部通过系统等条件自动选择策略

使用样例:

cpp 复制代码
int Add(int num1, int num2)
{
    cout << "加法111" << endl;
    std::this_thread::sleep_for(chrono::seconds(3));
    cout << "加法222" << endl;
    return num1 + num2;
}
int main()
{
    cout << "----------1----------" << endl;
    future<int> result = async(launch::async, Add, 11, 90);
    this_thread::sleep_for(chrono::seconds(1));
    cout << "----------2----------" << endl;
    int sum = result.get();  
    cout << "----------3----------" << endl;
    cout << sum << endl;  
    return 0;
}  
std::packaged_task
  • 介绍: packaged_task 就是将任务和 future 绑定在一起的模板,是一种对任务的封装。我们可以通过 std::packaged_task 对象获取任务相关联的 future 对象,通过调用 get_future()方法获得。packaged_task 的模板参数是函数签名。可以把 future 和 async 看成是分开的, 而 packaged_task 则是一个整体。

使用样例:

cpp 复制代码
int Add(int num1, int num2) {
    this_thread::sleep_for(chrono::seconds(3));
    return num1 + num2;
}
int main()
{
    //packaged_task<int(int,int)> task(Add);
    //future<int> fu = task.get_future();
    //task(11, 22);  
    //task可以当作一个可调用对象来调用执行任务
    //但是它又不能完全的当作一个函数来使用
	
    auto ptask = make_shared<packaged_task<int(int,int)>>(Add);
    future<int> fu = ptask->get_future();
    thread thr([ptask](){
        cout << "thread start" << endl;
        (*ptask)(11, 22);
    });
    int sum = fu.get();
    cout << "------------------------------" << endl;
    cout << sum << endl;
    thr.join();
    return 0;
}
std:::promise
  • 介绍: promise 提供了一种设置值的方式,它可以在设置之后通过相关联的 future 对象进行读取。:future 可以读取一个异步函数的返回值了, 但是要等待就绪,而 promise 就提供一种 方式手动让 future 就绪

使用样例:

cpp 复制代码
void Add(int num1, int num2, promise<int>& prom)
{
    prom.set_value(num1 + num2);
    return ;
}
int main()
{
    promise<int> prom;
    future<int> fut = prom.get_future();   //将prom 与 fut 绑定
    thread thr(Add, 11, 22, ref(prom));
    int result = fut.get();
    cout << "result: " << result << endl;
    thr.join();
    return 0;
}

下面开始线程池的实现

  • 设计思路: 线程池执行任务的时候,入口函数内部执行逻辑是固定的,因此选择 packaged_task 加上 future 的组合来实现。
  • 线程池工作思想: 用户传入要执行的函数,以及需要处理的数据(函数的参数),由线程池中的工作线程来执行函数完成任务实现。
  • 管理的成员:
任务池: 用 vector 或 queue 维护的一个函数任务池子
互斥锁 & 条件变量: 实现互斥同步功能
工作线程: 工作线程数量可以自己根据任务数量决定,用于不断从任务池取出任务执行任务
是否运行标志: 以便于控制线程池的结束。
  • 管理的操作:
入队任务: 入队一个函数和及其参数
停止运行: 终止线程池

具体函数的实现:

构造函数:
初始化成员变量,将线程在函数入口处等待
cpp 复制代码
threadpool(int thr_count = 1)
        :_stop(false)
    {
        for(int i = 0; i < thr_count; i++)
        {
            _threads.emplace_back(&threadpool::entry, this);
        }
    }
//线程入口函数---内部不断地从任务池中取出任务进行执行
void entry()
    {
        while(!_stop)
        {
            //为了防止每次只取出一个任务,频繁的加锁和解锁,所以进行下面操作
            vector<Functor> tmp_taskpool;
            {
                //加锁
                unique_lock<mutex> lock(_mutex);
                //等待任务池非空 || 线程池停止
                _cv.wait(lock, [this](){ return!_taskpool.empty() || _stop; });
                //取出任务来执行
                tmp_taskpool.swap(_taskpool);
            }
            for(auto& task : tmp_taskpool)
            {
                task();
            }
        }
    }
上述代码中 emplace_back 和 push_back 都可以,但 emplace_back 效率更高,_threads.push_back(std::thread(&threadpool::entry, this));// 1. 创建临时 std::thread 对象// 2. 将临时对象移动到 vector 中 _threads.emplace_back(&threadpool::entry, this);// 1. 在 vector 的内存位置直接构造 std::thread 对象// 2. 没有临时对象,更高效
push函数:
传进来的是用户要执行的函数,接下来是不定参,表示要处理的数据,也就是要传入到函数的参数, push函数内部会将这个传入的函数封装成一个异步任务(packaged_task),使用lambda生成一个可调用对象(内部执行异步任务),抛入到任务池中,由工作线程取出进行执行;
cpp 复制代码
 template<typename F, typename ...Args>
 auto push(F&& func, Args&&... args) -> future<decltype(func(args...))> //返回值类型为传入的函数的返回值类型
    {
        //1.将传入的函数封装成一个packaged_task任务   
        using return_type = decltype(func(args...));
        auto tmp_func = bind(forward<F>(func), forward<Args>(args)...); //完美转发以及参数包的展开
        auto task = make_shared<packaged_task<return_type()>>(tmp_func);
        future<return_type> fu = task->get_future(); 
        //2.构造一个lambda匿名函数(捕获任务对象),函数内执行任务对象
        {
            unique_lock<mutex> lock(_mutex);
            //3.将构造出来的匿名函数对象,抛入到任务池中
            _taskpool.emplace_back([task]() { 
                (*task)();
            });
            _cv.notify_one(); //通知一个线程进行任务执行
        }
        return fu;
    }
stop函数:
cpp 复制代码
void stop()
    {
        if(_isrunning == false) return;
        _isrunning = false;
        _cv.notify_all();
        for(auto& thr : _threads)
        {
            //回收线程
            thr.join();
        }
    }
析构函数:
cpp 复制代码
 ~threadpool()
    {
        stop();
    }

代码总览:

cpp 复制代码
#include <iostream>
#include <thread>
#include <condition_variable>
#include <mutex>
#include <functional>
#include <vector>
#include <atomic>
#include <future>
#define DEFAULT_NUM 5
class threadpool
{
    using functor = std::function<void()>;
public:
    threadpool(const int tnum = DEFAULT_NUM)
        :_isrunning(true)
    {
        for(int i = 0; i < tnum; i++)
        {
            _threads.emplace_back(&threadpool::entry, this);
        }
    }

    template<typename F, typename ...Args>
    auto push(F&& func, Args&&... args) -> std::future<decltype(func(args...))> 
    {
        
        using return_type = decltype(func(args...));
        auto tmp_func = std::bind(std::forward<F>(func), std::forward<Args>(args)...);
        auto task = std::make_shared<std::packaged_task<return_type()>>(tmp_func);
        {
	        std::unique_lock<std::mutex> lock(_mutex);
	        _taskpool.emplace_back([task](){
	            (*task)();
	        });
	        _cv.notify_one();
        }
        return task->get_future();
    }
    void stop()
    {
        if(_isrunning == false) return;
        _isrunning = false;
        _cv.notify_all();
        for(auto& thr : _threads)
        {
            //回收线程
            thr.join();
        }
    }
    ~threadpool()
    {
        stop();
    }
private:
    void entry()
    {
        while(_isrunning)
        {
            std::vector<functor> tmp_taskpool;
            {
                //加锁
                std::unique_lock<std::mutex> lock(_mutex);
                //等待
                _cv.wait(lock, [this](){
                    //唤醒的线程判断是否满足条件
                    return !_isrunning || !_taskpool.empty();
                });
                tmp_taskpool.swap(_taskpool);
            }
            //做任务
            for(auto& task : tmp_taskpool)
            {
                task();
            }
        }
    }
private:
    std::atomic<bool> _isrunning;
    std::vector<functor> _taskpool; //任务池
    std::mutex _mutex;
    std::condition_variable _cv;
    std::vector<std::thread> _threads;
};

int Add(int x, int y)
{
    return x + y;
}
int main()
{
    threadpool pool;
    for(int i = 0; i < 10; i++)
    {
        auto fut = pool.push(Add, 10, i);
        std::cout << "result : " << fut.get() << std::endl;
    }
    pool.stop();
    return 0;
}
相关推荐
一条咸鱼_SaltyFish1 小时前
远程鉴权中心设计:HTTP 与 gRPC 的技术决策与实践
开发语言·网络·网络协议·程序人生·http·开源软件·个人开发
我即将远走丶或许也能高飞1 小时前
vuex 和 pinia 的学习使用
开发语言·前端·javascript
沐知全栈开发1 小时前
SQL LEN() 函数详解
开发语言
剑锋所指,所向披靡!1 小时前
C++之类模版
java·jvm·c++
钟离墨笺1 小时前
Go语言--2go基础-->基本数据类型
开发语言·前端·后端·golang
小郭团队2 小时前
1_7_五段式SVPWM (传统算法反正切+DPWM3)算法理论与 MATLAB 实现详解
开发语言·嵌入式硬件·算法·matlab·dsp开发
鱼跃鹰飞2 小时前
Leetcode347:前K个高频元素
数据结构·算法·leetcode·面试
C+-C资深大佬2 小时前
C++风格的命名转换
开发语言·c++
No0d1es2 小时前
2025年粤港澳青少年信息学创新大赛 C++小学组复赛真题
开发语言·c++
点云SLAM2 小时前
C++内存泄漏检测之手动记录法(Manual Memory Tracking)
开发语言·c++·策略模式·内存泄漏检测·c++实战·new / delete