当前位置: 首页 > article >正文

C++11异步操作——std::future

C++11异步操作

  • 1. std::future
  • 2. std::async
  • 3. std::packaged_task
  • 4. std::promise

1. std::future

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

应用场景

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

这里需要注意的是future本质上不是一个异步任务,而是一个辅助异步操作的获取任务结果的。所以future并不能单独使用,而是需要搭配一些能够执行异步操作任务的模板或函数一起使用。

可搭配使用的有:

  • std::async函数模板:异步指向一个函数,返回一个future对象用于获取结果
  • std::packaged_task:类模板:为一个函数生成一个异步任务对象(课调用对象),用于在其他线程中执行
  • std::promise:类模板:实例化的对象,可以返回一个对象,在其他线程中向promise对象设置数据,其他线程的关联future就可以获取数据。

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

使用注意事项:

  • 选择合适的启动策略(launch policy):一定要指定一个启动策略,如果没有明确指定启动策略,std::launch::asyncstd::launch::deferred都不是默认策略,而是undefined,他是一个任务程序设计,内部有一个调度器(线程池)会根据实际情况决定采用那个策略。
  • 保证获取结果(future.get)std::async返回一个futrue对象,一定要采用future.get()或者future.wait()来获取任务结果或者等待任务完成。如果没有获取结果或者等待任务完成,程序会被视为是不正确的。
  • 异常处理:当异步任务出现异常时,这些异常会被future.get()捕获,一定要使用future.get()时准备好捕获和处理可能的异常。
  • 确定async对象的声明周期:如果std::async获取的std::future未被移动或者绑定到引用,则完整表达式结尾
  • 结果共享std::shared_future:有时候可能需要多个线程访问同一个异步结果,这个时候就可以用到std::shared_future。它可以让多个线程共享一个异步结果,而不是使用单一的std::futrue,确保多线程访问时的安全性。
#include <iostream>
#include <future>


// 策略   执行的异步任务 异步任务的参数
int Add(int num1, int num2){
    std::cout << "into Add" << std::endl;
    return num1 + num2;
}

int main()
{
    // std::launch::async策略:内部创建一个线程指向函数,函数运行结果通过future获取
    // std:: launch::deferred策略:同步策略,获取结果的时候再去执行函数
    // std::future<int> res = std::async(std::launch::async, Add, 1, 2);
    std::future<int> res = std::async(std::launch::deferred, Add, 1, 2);

    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "exe main" << std::endl;
    // 主线程休眠一秒中
    std::cout << res.get() << std::endl;
    return 0;
}

std::launch::async执行结果

into Add
exe main
3

std::launch::deferred执行结果

exe main
into Add
3

3. std::packaged_task

std::packaged_task就是将任务和std::future绑定在⼀起的模板,是⼀种对任务的封装。我们可以通过std::packaged_task对象获取任务相关联的std::future对象,通过调用get_future()方法获得。
std::packaged_task的模板参数是函数签名。可以把std::future和std::async看成是分开的,而std::packaged_task则是⼀个整体。

也就是说std::packaged_task是一个模板类,是一个任务包,是对一个函数模板进行的二次封装,封装成一个可调用的对象作为任务放到线程里去执行。任务包封装好了以后,可以在任意位置进行调用,通过关联的future来获取执行结果。

指向流程一般是:

  • 封装任务
  • 指向任务
  • 通过future获取执行后的结果,
#include <iostream>
#include <future>
#include <thread>


// 策略   执行的异步任务 异步任务的参数
int Add(int num1, int num2){
    std::cout << "into Add" << std::endl;
    return num1 + num2;
}

int main()
{
    // 1. 封装任务
    auto task = std::make_shared<std::packaged_task<int(int,int)>>(Add); // packaged_task底层不允许拷贝构造

    // 2. 获取任务包关联的future对象
    std::future<int> res = task->get_future();

    // 创建新线程执行封装好的任务包
    std::thread thr([task](){
        (*task)(1, 2);
    });
     //std::thread(task, 1, 2); //---错误⽤法 
	 //因为packaged_task禁⽌了拷⻉构造, 
	 //且因为每个packaged_task所封装的函数签名都有可能不同,因此也⽆法当作参数⼀样传递 
	 //传引⽤不可取,毕竟任务在多线程下执⾏存在局部变量声明周期的问题,因此不能传引⽤ 
	 //因此想要将⼀个packaged_task进⾏异步调⽤, 
	 //简单⽅法就只能是new packaged_task,封装函数传地址进⾏解引⽤调⽤了 
	 //⽽类型不同的问题,在使⽤的时候可以使⽤类型推导来解决 

    // 3. 获取结果
    std::cout << res.get() << std::endl;
    thr.join();
    return 0;
}

4. std::promise

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

一般的执行流程:

  • 使用的时候,就是先实例化一个指定结果的promise对象
  • 通过promise对象,获取关联的future对象
  • 在任意位置给promise设置数据,就可以通过关联的future获取这个设置的数据了
#include <iostream>
#include <future>
#include <thread>


// 策略   执行的异步任务 异步任务的参数
int Add(int num1, int num2){
    std::cout << "into Add" << std::endl;
    return num1 + num2;
}

int main()
{
    // 1. 实例化出一个promise对象
    std::promise<int> pro;

    // 2. 通过promise获取关联的future对象
    std::future<int> res = pro.get_future();

    // 3. 给promise设置值,后期就可以通过future获取promise中的值
    std::thread thr([&pro](){
        // 执行函数
        int ret = Add(1, 2);
        // 将值设置进promise中
        pro.set_value(ret);
    });

    std::cout << res.get() << std::endl;
    thr.join();
    return 0;
}

http://www.kler.cn/a/406496.html

相关文章:

  • 工具学习_Docker
  • 【IDER、PyCharm】免费AI编程工具完整教程:ChatGPT Free - Support Key call AI GPT-o1 Claude3.5
  • Java安全—JNDI注入RMI服务LDAP服务JDK绕过
  • 在 Taro 中实现系统主题适配:亮/暗模式
  • 探索 Vue.js:构建交互式前端的强大工具
  • 集成金蝶云星空数据至MySQL的完整案例解析
  • 即时通讯app入侵了 怎么办?
  • 浦语提示词工程实践(LangGPT版,服务器上部署internlm2-chat-1_8b,踩坑很多才完成的详细教程,)
  • IAR与鸿轩科技共同推进汽车未来
  • 实验07---7-03 n个数存入数组,输出下标奇数的元素
  • 代理IP:苹果Siri与ChatGPT Plus融合的关键助力
  • Android上运行Opencv(TODO)
  • 机器学习周志华学习笔记-第3章<线性模型>
  • 【阅读记录-章节3】Build a Large Language Model (From Scratch)
  • 掌上单片机实验室 – RT-Thread + ROS2 初探(25)
  • 【FTHR-G0001开发板测评】简介、程序测试
  • 不用 SQL 的数据仓库
  • leetcode-11-盛最多水的容器
  • 使用 Java Stream 优雅实现List 转化为Map<key,Map<key,value>>
  • 6、PyTorch中搭建分类网络实例
  • 对抗样本存在的原因
  • 鸿蒙NEXT开发-Navigation组件导航
  • 用 Python 写了一个俄罗斯方块小游戏(附源码)
  • 机器人打包物品研究现状简述
  • stm32启动过程解析startup启动文件
  • 【分享一个vue指令】鼠标放置提示指令v-tooltip