C++ 11异步操作类

1 基本用法

1.1引入异步操作类的原因

thread对象,它是C++11中提供异步创建多线程的工具。我们想要从线程中返回异步任务结果,一般需要依靠全局变量;从安全角度看,有些不妥;为此C++11提供了std::future类模板,future对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果。

int a = 1;
std::thread thread([a](int b) {
    return a + b;
}, 2);

thread.join();

不能获取线程的执行结果。

异步操作类封装了新建线程执行任务,同时获取任务返回值操作。

std::asyanc是std::future的高级封装, 一般我们不会直接使用std::futrue,而是使用对std::future的高级封装std::async。

1.2 异步操作类的分类

<future> 头文件中包含了以下几个类和函数:

  • Providers 类:std::promise, std::package_task
  • Futures 类:std::future, shared_future.
  • Providers 函数:std::async()
  • 其他类型:std::future_error, std::future_errc, std::future_status, std::launch.

c++11 异步操作相关的类,主要有std::future、std::promise、std::package_task。

  • std::future 作为异步操作结果的传输通道,方便获取线程函数的返回值
  • std::promise 用来包装一个值,将数据和future绑定起来,方便线程赋值。
  • std::package_task 用来包装一个可调用对象,将函数和future绑定起来。

1.2.1 std::future

   std::future 用来访问异步操作的结果,异步操作的结果不能马上获取,只能在未来某个时候从某个地方获取。future_status 有如下3中状态:

  • Deferred ,异步操作还没开始;
  • Ready,异步操作已经完成;
  • Timeout,异步操作超时。

可以通过查询future的状态,知道异步任务的执行情况。获取future 结果的3中方式:

  • get    #等待异步操作结束并返回结果
  • wait  #等待异步操作完成
  • wait_for  #超时等待返回结果

std::future 与std::shared_future

std::future 与 std::shared_future 来获取异步调用的结果,future 是不可以拷贝的,只能移动,share_future 是可以拷贝的,将future 放到容器中则需要share_future。

std::future 对象是std::async、std::promise、std::packaged_task的底层对象,用来传递其他线程中操作的数据结果。

应用举例:

#include <future>
#include <iostream>
#include <stout/stringify.hpp>

bool is_prime(int x)
{
  for (int i=0; i<x; i++)
  {
    if (x % i == 0)
      return false;
  }
  return true;
}

int main()
{
  std::future<bool> fut = std::async(is_prime, 700020007);
  std::cout << "please wait";
  std::chrono::milliseconds span(100);
  while (fut.wait_for(span) != std::future_status::ready)
    std::cout << ".";
  std::cout << std::endl;

  bool ret = fut.get();
  std::cout << "final result: " << stringify(ret) << std::endl;
  return 0;
}

     std::async会首先创建线程执行is_prime(700020007), 任务创建之后,std::async立即返回一个std::future对象。

 主线程既可使用std::future::get获取结果,如果调用过程中,任务尚未完成,则主线程阻塞至任务完成。

 主线程也可使用std::future::wait_for等待结果返回,wait_for可设置超时时间,如果在超时时间之内任务完成,则返回std::future_status::ready状态;如果在超时时间之内任务尚未完成,则返回std::future_status::timeout状态。

1.3 使用std::promise 和std::future

将std::promise和future 绑定起来,为获取线程函数中的某个值提供便利。std::promise的作用就是提供一个不同线程之间的数据同步机制,它可以存储一个某种类型的值,并将其传递给对应的future, 即使这个future不在同一个线程中也可以安全的访问到这个值

std::promise提供了在一个线程中设置变量,在另一个线程中获取的能力。对我们在线程间同步数据提供了一种解决方案。

promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。

int a = 1;
std::promise<int> res;
std::future<int> future = res.get_future();//promise 与future绑定

std::thread thread([a](int b, std::promise<int>& res) {    //线程函数中为外面传进来的promise赋值
    res.set_value(a + b);
}, 2, res);

std::cout << future.get() << std::endl;//线程函数执行完成后可以通过promise 的future 获取该值

 举例:

#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

void print_int(std::future<int>& fut) {
    int x = fut.get(); // 获取共享状态的值.
    std::cout << "value: " << x << '\n'; // 打印 value: 10.
}

int main ()
{
    std::promise<int> prom; // 生成一个 std::promise<int> 对象.
    std::future<int> fut = prom.get_future(); // 和 future 关联.
    std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t.
    prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.
    t.join();
    return 0;
}

 输出: 

value: 10

详解:

std::promise::get_future 介绍:该函数返回一个与 promise 共享状态相关联的 future 返回的 future 对象可以访问由 promise 对象设置在共享状态上的值或者某个异常对象。只能从 promise 共享状态获取一个 future 对象。

std::promise::set_value 介绍:设置共享状态的值,此后 promise 的共享状态标志变为 ready。

1.4 使用std::package_task 和std::future

std::package 包装了一个可调用对象的包装类(如function、lambda、bind),将函数和future 绑定起来,以便异步调用。std::promise 保存一个共享状态的值,而 packaged_task 保存的是一个函数。

int a = 1;
std::packaged_task<int(int)> task = [a](int b) {
    return a + b;
};
std::future<int> future = task.get_future();
task(2);
std::cout << future.get() << std::endl; // 3

举例:

// packaged_task example
#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
  for (int i=from; i!=to; --i) {
    std::cout << i << '\n';
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  std::cout << "Lift off!\n";
  return from-to;
}

int main ()
{
  std::packaged_task<int(int,int)> tsk (countdown);   // set up packaged_task
  std::future<int> ret = tsk.get_future();            // get future

  std::thread th (std::move(tsk),10,0);   // spawn thread to count down from 10 to 0

  // ...

  int value = ret.get();                  // wait for the task to finish and get result

  std::cout << "The countdown lasted for " << value << " seconds.\n";

  th.join();

  return 0;
}

 输出结果:

10
9
8
7
6
5
4
3
2
1
Lift off!
The countdown lasted for 10 seconds.

1.5 使用 std::async

std::async比std::promise、std::packaged_task和std::thread 更高一层,可以用来直接创建异步的task,异步任务返回的结果保存在future中。当需要获取异步任务的结果时,直接调用future.get() 方法即可,如果不关注异步任务的结果,只是简单地等待任务完成,则调用future.wait() 方法。

std::future<int> future3 = std::async([a](int b) {
    return a + b;
}, 2);

std::future<int> future4 = std::async([a](int b) {
    return a + b;
}, 3);

std::cout << future3.get() + future4.get() << std::endl; // 3+4=7

 std::async

C++11中的std::async是个模板函数。std::async异步调用函数,在某个时候以Args作为参数(可变长参数)调用Fn,无需等待Fn执行完成就可返回,返回结果是个std::future对象。

模板函数 async 异步地运行函数 f (潜在地在可能是线程池一部分的分离线程中),并返回最终将保有该函数调用结果的 std::future 。

std::async的启动策略类型是个枚举类enum class launch,包括:

1. std::launch::async:异步,启动一个新的线程调用Fn,该函数由新线程异步调用,并且将其返回值与共享状态的访问点同步。(在调用async 时开始创建线程

2. std::launch::deferred:延迟,在访问共享状态时该函数才被调用。对Fn的调用将推迟到返回的std::future的共享状态被访问时(使用std::future的wait或get函数)。(延迟加载方式创建线程,调用async时不创建线程,直到调用future的get或wait时才创建线程

参数Fn:可以为函数指针、成员指针、任何类型的可移动构造的函数对象(即类定义了operator()的对象)。Fn的返回值或异常存储在共享状态中以供异步的std::future对象检索。

参数Args:传递给Fn调用的参数,它们的类型应是可移动构造的。

返回值:当Fn执行结束时,共享状态的std::future对象准备就绪。std::future的成员函数get检索的值是Fn返回的值。当启动策略采用std::launch::async时,即使从不访问其共享状态,返回的std::future也会链接到被创建线程的末尾。在这种情况下,std::future的析构函数与Fn的返回同步。

std::async的特点:

  • std::future可以从异步任务中获取结果,一般与std::async配合使用,std::async用于创建异步任务,实际上就是创建一个线程执行相应任务。
  • std::async就是异步编程的高级封装,封装了std::future的操作,基本上可以代替std::thread 的所有事情。
  • std::async的操作,其实相当于封装了std::promise、std::packaged_task加上std::thread。

参考文献:

【1】C++11之std::future对象使用说明: https://blog.csdn.net/c_base_jin/article/details/89761718

【2】C++11 使用异步编程std::async和std::futurehttps://www.cnblogs.com/moodlxs/p/10111601.html

【3】C++11 并发指南四(<future> 详解一 std::promise 介绍)https://www.cnblogs.com/haippy/p/3239248.html

【4】C++ 并发编程实战:http://www.j9p.com/down/527914.html 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值