C++11 异步操作实现线程池

std::future

介绍

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

应用场景

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

用法示例

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 内部通过系统等条件自动选择策略

std::launch::async :

cpp 复制代码
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int Add(int num1, int num2)
{
    std::cout << "加法!111\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "加法!222\n";
    return num1 + num2;
}

int main()
{
    // std::async(func, ...)      std::async(policy, func, ...)
    std::cout << "----------1----------" << std::endl;
    // std::launch::deferred  在执行get获取异步结果的时候,才会执行异步任务
    // std::launch::async   内部会创建工作线程,异步的完成任务
    std::future<int> result = std::async(Add, 11, 22);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "----------2----------" << std::endl;
    int sum = result.get();
    std::cout << "----------3----------" << std::endl;
    std::cout << sum << std::endl;
    return 0;
}

运行结果:

bash 复制代码
----------1----------
加法!111
----------2----------
加法!222
----------3----------
33

std::launch::deferred:

cpp 复制代码
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int Add(int num1, int num2)
{
    std::cout << "加法!111\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "加法!222\n";
    return num1 + num2;
}

int main()
{
    // std::async(func, ...)      std::async(policy, func, ...)
    std::cout << "----------1----------" << std::endl;
    // std::launch::deferred  在执行get获取异步结果的时候,才会执行异步任务
    // std::launch::async   内部会创建工作线程,异步的完成任务
    std::future<int> result = std::async(std::launch::deferred, Add, 11, 22);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "----------2----------" << std::endl;
    int sum = result.get();
    std::cout << "----------3----------" << std::endl;
    std::cout << sum << std::endl;
    return 0;
}

运行结果:

bash 复制代码
----------1----------
----------2----------
加法!111
加法!222
----------3----------
33

promise

std::promise 提供了一种设置值的方式,它可以在设置之后通过相关联的std::future对象进行读取。

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>

void Add(int num1,int num2,std::promise<int>&prom){
    prom.set_value(num1+num2);
    return ;
}

int main(){
    std::promise<int> prom;
    std::future<int> fu = prom.get_future();//设置同步关系
    std::thread thr(Add,11,22,std::ref(prom));
    int res = fu.get();
    std::cout<<"sum: "<<res<<std::endl;
    thr.join();
    return 0;
}

运行结果:

bash 复制代码
sum: 33

packaged_task

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

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <memory>

int Add(int num1,int num2){
    std::this_thread::sleep_for(std::chrono::seconds(3));
    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 thr([ptask](){
        (*ptask)(11,22);
    });
    int sum = fu.get();
    std::cout<< sum <<std::endl;
    thr.join();
    return 0;
}

运行结果:

bash 复制代码
33

线程池实现

最终我们就借助第三种方式packaged_task来实现线程池。

线程池的工作思想: 用户传入要执行的函数,以及需要处理的数据(函数的参数),由线程池中的工作线程来执行函数完成任务

实现:

  • 管理的成员

    任务池:用vector维护的一个函数任务池子

    互斥锁 & 条件变量: 实现同步互斥

    一定数量的工作线程:用于不断从任务池取出任务执行任务

    结束运行标志:以便于控制线程池的结束。

  • 管理的操作:

    入队任务:入队一个函数和参数

    停止运行:终止线程池

具体实现:

cpp 复制代码
#include <iostream>
#include <functional>
#include <memory>
#include <thread>
#include <future>
#include <mutex>
#include <condition_variable>
#include <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函数内部,会将这个传入的函数封装成一个异步任务(packaged_task),
    // 使用lambda生成一个可调用对象(内部执行异步任务),抛入到任务池中,由工作线程取出进行执行
    template <class F, class... Args>
    auto push(const 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> tmp_taskpool;
            {
                // 加锁
                std::unique_lock<std::mutex> lock(_mutex);
                // 等待任务池不为空,或者_stop被置位返回,
                _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;
};

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;
}

运行结果:

bash 复制代码
11
12
13
14
15
16
17
18
19
20
相关推荐
!停2 小时前
C++入门STL容器Vector使用基础,深挖 Vector替代 C 语言繁琐容器的利器
开发语言·c++
2401_871492852 小时前
Vue.js计算属性computed依赖追踪与副作用函数effect关联机制
jvm·数据库·python
2401_882273722 小时前
SQL如何快速提取分组中最晚时间点数据_结合窗口函数实现
jvm·数据库·python
Lumos_7772 小时前
Linux -- 共享内存
java·linux·运维
tankeven2 小时前
C++ 学习杂记06:std::unordered_map
c++
t***5442 小时前
如何在 Dev-C++ 中设置 MinGW 和 Clang 的路径
java·前端·c++
2301_814809862 小时前
如何用 cookie 的 HttpOnly 与 Secure 属性防范 XSS 攻击
jvm·数据库·python
m0_515098422 小时前
如何用 Object.keys 与 getOwnPropertyNames 遍历键名
jvm·数据库·python
拜托啦!狮子2 小时前
安装EnsDb.Hsapiens.v86
java·服务器·前端