仿RabbitMq实现简易消息队列基础篇(future操作实现异步线程池)

@TOC

介绍

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

应用场景

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

使用方法

std::async

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

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

  2. std::launch::async表明函数会在自己创建的线程上运行

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

    #include <iostream>
    #include <future>
    #include <thread>
    #include <chrono>

    using namespace std;

    int async_task()
    {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 2;
    }

    int main()
    {
    // 关联异步任务async_task 和 future
    std::future<int> result = std::async(std::launch::deferred | std::launch::async, async_task);
    // 此时可以执行其他操作
    cout << "干其他事情" << endl;
    // 获取异步任务结果
    int ret = result.get();
    cout << ret << endl;
    return 0;
    }

std::packaged_task

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

所谓函数签名就是一个函数头去掉函数名

下面介绍一下std::packaged_task,首先这个类型的对象是不可复制的

可以看到拷贝构造函数被delete了。

std::packaged_task是用来包装异步任务的工具,它的本质是将一个可调用对象封装起来,和std::future结合起来,这个不能被直接调用,因为这样的实质是同步调用任务,而不是异步调用,并且std::packaged_task对象是没有返回值的,因为是不可拷贝的, 所以std::packaged_task对象在使用的时候,需要创建一个线程,然后使用智能指针或者move函数来进行传递。注意因为创建了一个新的线程,并且需要获取到这个新的线程执行任务的结果,所以我们就需要进行等待或者分离,即join和detach()。

使用move进行移动

#include <iostream>
#include <future>
#include <thread>
#include <memory>
#include <chrono>

int Add(int num1, int num2)
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return num1 + num2;
}

int main()
{
    std::packaged_task<int(int, int)> task(Add);
    std::future<int> fu = task.get_future();
    std::thread th(std::move(task), 11, 22);

    int ret = fu.get();
    std::cout << ret << std::endl;
    
    th.join();
    return 0;
}

使用智能指针

#include <iostream>
#include <future>
#include <thread>
#include <memory>
#include <chrono>

int Add(int num1, int num2)
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return num1 + num2;
}

int main()
{
    auto ptask = std::make_shared<std::packaged_task<int(int, int)>>(Add);
    std::future<int> fu = ptask->get_future();
    std::thread th([ptask]()
    {
        (*ptask)(11, 22);
    });

    int ret = fu.get();
    std::cout << ret << std::endl;
    
    th.join();
    return 0;
}

std::promise

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

#include <iostream>
#include <future>
#include <chrono>

void task(std::promise<int> result_promise)
{
    int result = 2;
    result_promise.set_value(result);
    std::cout << result << std::endl;
}


int main()
{
    std::promise<int> result;
    std::future<int> reuslt_future = result.get_future();

    std::thread th(task, std::move(result));

    int ret = reuslt_future.get();
    std::cout << ret << std::endl;
    th.join();
    return 0;
}

线程池设计

#include <iostream>
#include <functional>
#include <memory>
#include <thread>
#include <future>
#include <mutex>
#include <condition_variable>
#include <vector>

class threadPool
{
public:
    using ptr = std::shared_ptr<threadPool>;
    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();
    }
    // push传入的是首先有一个函数--用户要执行的函数,接下来是不定参,标识要处理的数据就是要传入到函数中的参数
    // push函数内部,会将这个传入的函数封装成一个异步任务(packaged_task),
    // 使用 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(std::forward<Args>(args)...)>>(std::forward(func));
        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;
    }

    void stop()
    {
        if(_stop == true) return ;
        _stop = true;
        _cv.notify_all();
        for (auto &thread : _threads)
        {
            thread.join();
        }
    }

private:
    // 线程入口函数---内部不断的从任务池中取出任务进行执行
    void entry()
    {
        while (!_stop)
        {
            std::vector<Functor> tmp_taskpool;
            {
                // 加锁
                std::unique_lock<std::mutex> lock(_mutex);
                // 等待任务池不为空,或者_stop 被置位返回true
                _cv.wait(lock, [this]()
                         { return _stop || !_taskpool.empty(); });
                // 取出任务进行执行
                tmp_taskpool.swap(_taskpool);   // 这里是将全局任务池里面的任务全部给一个线程里面的任务池    
            }
            for (auto &task : tmp_taskpool)
            {
                task();
            }
        }
    }

private:
    std::atomic<bool> _stop;
    std::vector<Functor> _taskpool;   // 任务池
    std::mutex _mutex;              // 互斥锁
    std::condition_variable _cv;    // 条件变量
    std::vector<std::thread> _threads;
};

main函数

#include "threadpool.hpp"


int Add(int num1, int num2)
{
    return num1 + num2;
}

int main()
{
    threadPool pool;
    for(int i = 0; i < 10; i++)
    {
        std::future<int> fu = pool.push(Add, 11, i);
        std::cout << fu.get() << std::endl;
    }
    pool.stop();
    return 0;
}
相关推荐
Ajiang282473530429 分钟前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
中云DDoS CC防护蔡蔡31 分钟前
微信小程序被攻击怎么选择高防产品
服务器·网络安全·微信小程序·小程序·ddos
幽兰的天空34 分钟前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
HPC_fac130520678161 小时前
以科学计算为切入点:剖析英伟达服务器过热难题
服务器·人工智能·深度学习·机器学习·计算机视觉·数据挖掘·gpu算力
yaoxin5211233 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
Theodore_10224 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸5 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象5 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了5 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
----云烟----5 小时前
QT中QString类的各种使用
开发语言·qt