【仿RabbitMQ消息队列】基于C++11中packaged_tack异步线程池

目录

什么是同步和异步?

future

使用future和async配合管理异步任务

使用promise和future配合管理异步任务

使⽤std::packaged_task和std::future配合

C++11异步线程池


什么是同步和异步?

同步(Synchronous)

同步编程是指程序按照代码的顺序,一步步执行,每一步必须等待上一步执行完毕,才能执行下一步。在同步操作中,如果某个任务(如I/O操作、网络请求等)需要等待(比如等待磁盘读写完成、等待网络响应等),程序就会阻塞在那里,直到该任务完成,才能继续执行后续的代码。这种方式简单直观,但效率较低,尤其是在处理需要长时间等待的任务时,会导致程序响应变慢或"假死"。

异步(Asynchronous)

异步编程则允许程序在等待某个长时间运行的操作(如文件I/O、网络请求等)完成时,继续执行后续的代码,而不会阻塞当前线程。当异步操作完成时,通常通过回调函数、Promises、Future、async/await等方式来通知程序,以便执行后续依赖于该操作结果的操作。这种方式可以提高程序的效率和响应性,尤其是在处理多个并发任务时。


future

介绍

std::future是C++11标准库中的⼀个模板类,它表⽰⼀个异步操作的结果。当我们在多线程编程中使⽤异步任务时,std::future可以帮助我们在需要的时候获取任务的执⾏结果。std::future的⼀个重要特性是能够阻塞当前线程,直到异步操作完成,从⽽确保我们在获取结果时不会遇到未完成的操作。

简单来说:future就是可以用来获取一个异步任务/函数的结果的;

应用场景:

  • 异步任务: 当我们需要在后台执⾏⼀些耗时操作时,如⽹络请求或计算密集型任务等,std::future可以⽤来表⽰这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并⾏处理,从⽽提⾼程序的执⾏效率
  • 并发控制: 在多线程编程中,我们可能需要等待某些任务完成后才能继续执⾏其他操作。通过使⽤std::future,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执⾏后续操作
  • 结果获取:std::future提供了⼀种安全的⽅式来获取异步任务的结果。我们可以使⽤std::future::get()函数来获取任务的结果,此函数会阻塞当前线程,直到异步操作完成。这样,在调⽤get()函数时,我们可以确保已经获取到了所需的结果

使用future和async配合管理异步任务

std::async是⼀种将任务与std::future关联的简单⽅法。它创建并运⾏⼀个异步任务,并返回⼀个与该任务结果关联的std::future对象。默认情况下,std::async是否启动⼀个新线程,或者在等待future时,任务是否同步运⾏都取决于你给的 参数。这个参数为std::launch类型:

  • std::launch::deferred 表明该函数会被延迟调⽤,直到在future上调⽤get()或者wait()才会开始执⾏任务

  • std::launch::async 表明函数会在⾃⼰创建的线程上运⾏

  • std::launch::deferred | std::launch::async 内部通过系统等条件⾃动选择策略

    #include <iostream>
    #include <thread>
    #include <future>
    #include<chrono>
    using namespace std;
    int Add(int num1, int num2)
    {

      cout << "加法" << endl;
      return num1 + num2;
    

    }
    int main()
    {
    // 两个版本
    // 函数+不定参数
    // 策略+函数+不定餐
    cout<<"first "<<endl;
    //deferred: 在执行get获取异步结果的时候才会执行异步任务
    //async: 内部会创建工作线程异步完成任务
    future<int> result = async(std::launch::deferred, Add, 11, 22);
    // 返回值是什么参数就是什么
    std::this_thread::sleep_for(chrono::seconds(1));
    cout<<"two"<<endl;
    int sum=result.get();
    cout<<"three"<<endl;
    cout<<sum<<endl;
    return 0;
    }

使用promise和future配合管理异步任务

std::promise提供了⼀种设置值的⽅式,它可以在设置之后通过相关联的std::future对象进⾏读取。换种说法就是之前说过std::future可以读取⼀个异步函数的返回值了, 但是要等待就绪, ⽽std::promise就提供⼀种 ⽅式⼿动让 std::future就绪

我们可以把promise理解为主线程给另一个执行异步任务函数线程放返回值的篮子,但是这个篮子交给另一个线程后无法在给回来,因此主线程需要用promise创建一个future工具从这个篮子中去取返回值

#include<iostream>
#include<thread>
#include<future>
using namespace std;
void Add(int num1,int num2,promise<int> &prom)
{
    prom.set_value(num1+num2);
    return ;
}
int main()
{
    promise<int> prom;
    future<int> fu=prom.get_future();
    thread t1(Add,11,22,ref(prom));

    int res = fu.get();
    cout<<"sum: "<<res<<endl;
    t1.join();
    return 0;
}

使⽤std::packaged_task和std::future配合

std::packaged_task就是将任务和 std::feature 绑定在⼀起的模板,是⼀种对任务的封装。我们可以通过std::packaged_task对象获取任务相关联的std::feature对象,通过调⽤get_future()⽅法获得。std::packaged_task的模板参数是函数签名。

可以把std::future和std::async看成是分开的, ⽽ std::packaged_task则是⼀个整体。

#include<iostream>
#include<thread>
#include<future>
#include<memory>
using namespace std;
//package_task是一个模板类,实例化的对象可以对一个函数进行二次封装
int Add(int num1,int num2)
{
    return num1+num2;
}
int main()
{
    auto ptask = make_shared<packaged_task<int(int,int)>>(Add);
    future<int> fu = ptask->get_future();
    thread thr([ptask](){
        (*ptask)(11,22);
    });
    int sum = fu.get();
    cout<<sum<<endl;
    thr.join();
    return 0;
}

C++11异步线程池

对于普通的线程池我们只是将任务交给线程执行即可,但是如果我们想要获取任务执行的返回值就要进行类似上面的异步处理;基于线程池执⾏任务的时候,⼊⼝函数内部执⾏逻辑是固定的,因此选择std::packaged_task加上std::future的组合来实现。

线程池工作思想

⽤⼾传⼊要执⾏的函数,以及需要处理的数据(函数的参数),由线程池中的⼯作线程来执⾏函

数完成任务

实现

管理成员

  • 任务池:⽤vector维护的⼀个函数任务池⼦
  • 互斥锁 & 条件变量: 实现同步互斥
  • ⼀定数量的⼯作线程:⽤于不断从任务池取出任务执⾏任务
  • 结束运⾏标志:以便于控制线程池的结束

管理的操作

  • ⼊队任务:⼊队⼀个函数和参数
  • 停⽌运⾏:终⽌线程池

完整代码

class threadpool
{
public:
    using Functor = std::function<void(void)>;
    threadpool(int thr_count = 1) : _stop(false)
    {
        for (int i = 0; i < thr_count; i++)
        {
            _threads.emplace_back(&threadpool::entry, this);
        }
    }
    ~threadpool()
    {
        stop();
    }
    void stop()
    {
        if (_stop == true)
            return;
        _stop = true;
        _cv.notify_all();
        // 等待所有线程退出
        for (auto &thread : _threads)
        {
            thread.join();
        }
    }
    // push传入的首先有一个函数
    // 不定参数表示要传入函数的参数
    // push函数内部会将这个传入的函数封装成一个异步任务,抛入任务池中,由工作线程取出执行
    // 使用lambda表达式生成一个可调用对象,抛入到任务池中,由工作线程取出进行执行
    // 入队
    // 函数类型
    // 参数包
    template <typename F, typename... Args>
    auto push(F &&func, Args &&...args) -> std::future<decltype(func(args...))>
    {
        // 1.将传入的函数封装一个packaged_task任务
        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::future<return_type> fu = task->get_future();
        // 2。构造一个lambda表达式(捕获任务对象),函数内执行任务对象
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 3.将构造出来的匿名函数对象,抛入到任务池中
            _taskpool.push_back([task]()
                                { (*task)(); });

            _cv.notify_one();
        }
        return fu;
    }

private:
    // 线程入口函数
    // 不断地从任务池中取任务进行执行
    void entry()
    {
        while (!_stop)
        {
            std::vector<Functor> tem_taskpool;
            {
                // 加锁
                std::unique_lock<std::mutex> lock(_mutex);
                // 等待:等待任务池不为空,或者_stop被置位
                _cv.wait(lock, [this]()
                         { return _stop || !_taskpool.empty(); });
                // 取出任务执行
                tem_taskpool.swap(_taskpool);
            }
            for (auto &task : tem_taskpool)
            {
                task();
            }
        }
    }

private:
    std::atomic<bool> _stop;
    std::vector<Functor> _taskpool; // 任务池
    std::mutex _mutex;
    std::condition_variable _cv;
    std::vector<std::thread> _threads; // 线程池
};

上面异步线程池的完整代码中的语法规则都是C++11的新特性,在核心功能中甚至一行都有好几个的C++11新特性,要想理解上面的代码需要我们非常熟悉新特性;


今天对C++11异步线程池的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!!

相关推荐
阿史大杯茶1 分钟前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
陌小呆^O^10 分钟前
Cmakelist.txt之win-c-udp-server
c语言·开发语言·udp
C++忠实粉丝10 分钟前
计算机网络socket编程(3)_UDP网络编程实现简单聊天室
linux·网络·c++·网络协议·计算机网络·udp
Gu Gu Study17 分钟前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
时光の尘31 分钟前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
我们的五年36 分钟前
【Linux课程学习】:进程描述---PCB(Process Control Block)
linux·运维·c++
-一杯为品-41 分钟前
【51单片机】程序实验5&6.独立按键-矩阵按键
c语言·笔记·学习·51单片机·硬件工程
以后不吃煲仔饭1 小时前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师1 小时前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
前端拾光者1 小时前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化