C++11的异步操作让多线程开发变得简单
创始人
2024-05-25 13:52:14
0

C++11的异步操作

  • 简介
  • 一、std::future
    • 1.1、future的类型
    • 1.2、future的使用
    • 1.3、使用示例
  • 二、std::packaged_task
  • 三、std::promise
  • 总结

简介

C++提供如下的异步操作接口:

  1. std::future :异步指向某个任务,然后通过future特性去获取任务函数的返回结果。
  2. std::aysnc :异步运行某个任务函数。 std::promise :线程1设置了某个值,然后通知另外的线程,可以省去消息通信。
  3. std::packaged_task :将任务和feature绑定在一起的模板,是一种对任务的封装。

一、std::future

std::future期待一个返回,从一个异步调用的角度来说,future更像是执行函数的返回值,C++标准库使用std::future为一次性事件建模,如果一个事件需要等待特定的一次性事件,那么这线程可以获取一个future对象来代表这个事件。

异步调用往往不知道何时返回,但是如果异步调用的过程需要同步,或者说后一个异步调用需要使用前一个异步调用的结果;这个时候就要用到future。

线程可以周期性的在这个future上等待一小段时间,检查future是否已经ready,如果没有,该线程可以先去做另一个任务,一旦future就绪,该future就无法复位(无法再次使用这个future等待这个事件),所以future代表的是一次性事件。

1.1、future的类型

在库的头文件中声明了两种future,唯一future(std::future)和共享future(std::shared_future)这两个是参照std::unique_ptr和std::shared_ptr设立的,前者的实例是仅有的一个指向其关联事件的实例,而后者可以有多个实例指向同一个关联事件,当事件就绪时,所有指向同一事件的std::shared_future实例会变成就绪。

future的三种用法:

  • 使用模板自己定义类型,比如std::future test=std::async(func); 。
  • 使用decltype推导函数返回类型,比如std::future test=std::async(func);。
  • 使用auto,比如auto test=std::async(func);。

注意,使用decltype推导函数的返回类型,如果函数是带参数的,需要传入函数的参数值,而不是参数类型。

std::future test=std::async(func,1,2);//错误的
std::future test=std::async(func,1,2);//正确的

1.2、future的使用

std::future是一个模板,例如std::future,模板参数就是期待返回的类型,虽然future被用于线程间通信,但其本身却并不提供同步访问,热门必须通过互斥元或其他同步机制来保护访问。

future使用的时机是当你不需要立刻得到一个结果的时候,你可以开启一个线程帮你去做一项任务,并期待这个任务的返回,但是std::thread并没有提供这样的机制,这就需要用到std::async和std::future(都在头文件中声明)std::async返回一个std::future对象,而不是给你一个确定的值(所以当你不需要立刻使用此值的时候才需要用到这个机制)。当你需要使用这个值的时候,对future使用get(),线程就会阻塞直到future就绪,然后返回该值。

1.3、使用示例

#include 
#include 
#include 
using namespace std;int find_result_to_add()
{std::this_thread::sleep_for(std::chrono::seconds(2));// 用来测试异步延迟的影响return 1+1;
}int find_result_to_add2(int a,int b)
{std::this_thread::sleep_for(std::chrono::seconds(2));return a+b;
}void do_other_things()
{std::cout << "Hello World" << std::endl;
}
int main(int argc,char **argv)
{// 1future result=std::async(find_result_to_add);do_other_things();std::cout << "result: " << result.get() << std::endl;// 2future result2=async(find_result_to_add2,10,20);do_other_things();std::cout << "result2: " << result2.get() << std::endl;// 3auto result3=async(find_result_to_add2,10,20);//推荐std::cout << "result3: " << result3.get() << std::endl;return 0;
}

运行结果:

Hello World
result: 2
Hello World
result2: 30
result3: 30

跟thread类似,async允许你通过将额外的参数添加到调用中,来将附加参数传递给函数。如果传入的函数指针是某个类的成员函数,则还需要将类对象指针传入(直接传入,传入指针,或者是std::ref封装)。
默认情况下,std::async是否启动一个新线程,或者在等待future时,任务是否同步运行都取决于你给的参数。这个参数为std::launch类型:

  • std::launch::defered,表明该函数会被延迟调用,直到在future上调用get()或者wait()为止 。
  • std::launch::async,表明函数会在自己创建的线程上运行 。
  • std::launch::any = std::launch::defered | std::launch::async 。
  • std::launch::sync = std::launch::defered 。
enum class launch { async,deferred,sync=deferred,any=async|deferred 
};

注意:默认选项参数被设置为std::launch::any。如果函数被延迟运行可能永远都不会运行。

二、std::packaged_task

如果说std::async和std::feature还是分开看的关系的话,那么std::packaged_task就是将任务和feature绑定在一起的模板,是一种封装对任务的封装。

std::packaged_task包装任何可调用目标(函数、lambda表达式、绑定表达式或其他函数对象),以便可以异步调用它。它的返回值或抛出的异常存储在共享状态中,可以通过std::future对象访问。

可以通过std::packaged_task对象获取任务相关联的feature,调用get_future()方法可以获得std::packaged_task对象绑定的函数的返回值类型的future。std::packaged_task的模板参数是函数签名(例如int add(int a, intb)的函数签名就是int(int, int) )。

使用示例:

#include  
#include  
using namespace std; 
int add(int a, int b) 
{ return a + b; 
}
void do_other_things() {std::cout << "Hello World" << std::endl; 
}
int main() 
{ std::packaged_task task(add); //封装任务,不会执行add(...)std::this_thread::sleep_for(std::chrono::seconds(2));// 用来测试异步延迟的影响do_other_things(); std::future result = task.get_future(); // 只是获取futuretask(1, 1);                            // 必须要让任务执行,否则在get()获取future的值时会一直阻塞 std::cout << result.get() << std::endl; return 0; 
}

三、std::promise

从字面意思上理解promise代表一个承诺。promise比std::packaged_task抽象层次低。
std::promise提供了一种设置值的方式,它可以在这之后通过相关联的std::future对象进行读取。换种说法,之前已经说过std::future可以读取一个异步函数的返回值了,那么这个std::promise就提供一种方式手动让future就绪。

使用示例:

#include  
#include  
#include 
#include using namespace std; void print(std::promise& p)
{ p.set_value("There is the result whitch you want."); //set_value之后,就可以get()到
}
void do_some_other_things() 
{ std::cout << "Hello World" << std::endl; 
}
int main() 
{ std::promise promise; std::future result = promise.get_future(); std::thread t(print, std::ref(promise)); do_some_other_things(); std::cout << result.get() << std::endl; //等待promise的返回t.join(); return 0; 
}

由此可以看出在promise创建好的时候future也已经创建好了线程在创建promise的同时会获得一个future,然后将promise传递给设置他的线程,当前线程则持有future,以便随时检查是否可以取值。

总结

future的表现为期望,当前线程持有future时,期望从future获取到想要的结果和返回,可以把future当做异步函数的返回值。而promise是一个承诺,当线程创建了promise对象后,这个promise对象向线程承诺他必定会被人设置一个值,和promise相关联的future就是获取其返回的手段。

在这里插入图片描述

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...