前提:

C++ 11 中提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。多线程库对应的头文件是 #include <thread>,类名为 std::thread。

然而线程毕竟是比较贴近系统的东西,使用起来仍然不是很方便,特别是线程同步及获取线程运行结果上就更加麻烦。我们不能简单的通过 thread.join() 得到结果,必须定义一个线程共享的变量来传递结果,同时还要考虑线程间的互斥问题。好在 C++ 中提供了一个相对简单的异步接口 std::async ,通过这个接口可以简单的创建线程并通过std::future中获取结果。以往都是自己去封装线程实现自己的 async,现在有线程的跨平台接口可以使用就极大的方便了 C++ 多线程编程。

std::async 的函数原型

//(C++11 起) (C++17 前)
template< class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
async( Function&& f, Args&&... args ); //(C++11 起) (C++17 前)
template< class Function, class... Args >
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
async( std::launch policy, Function&& f, Args&&... args );

第一个参数是线程的创建策略,有两种策略可供选择:

  • std::launch::async: 在调用 async 就开始创建线程
  • std::launch::deferred: 延迟加载方式创建线程。调用 aysnc 时不创建线程,直到调用了 future 的 get 或者 wait 时才创建线程。

默认策略是:std::launch::async | std::launch::deferred 也就是两种策略的合集

第二个参数是线程函数

线程函数可以接受 function,lambda expression,bind expression,or another function object

第三个参数是线程函数的参数

不再说明

返回值 std::future

std::future 是一个模板类,它提供了一种访问异步操作结果的机制。从字面意思上看它表示未来,这个意思就非常贴切,因为它不是立即获取结果但是可以在某个时候以同步的方式来获取结果。我们可以通过查询 future 的状态来获取异步操作的结构。future_status 有三种状态:

  • deferred:异步操作还未开始
  • ready:异步操作已经完成
  • timeout:异步操作超时,主要用于 std::future<T>.wait_for()

示例

//查询 future 的状态
std::future_status status;
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred) {
std::cout << "deferred" << std::endl;
} else if (status == std::future_status::timeout) {
std::cout << "timeout" << std::endl;
} else if (status == std::future_status::ready) {
std::cout << "ready!" << std::endl;
}
} while (status != std::future_status::ready);

  

std::future 获取结果的方式有三种:

  • get:等待异步操作结束并返回结果
  • wait:等待异步操作结束,但没有返回值
  • waite_for:超时等待返回结果,上面示例中就是对超时等待的使用展示

std::async 的基本用法:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>
#include <string>
#include <mutex> std::mutex m;
struct X {
void foo(int i, const std::string& str) {
std::lock_guard<std::mutex> lk(m);
std::cout << str << ' ' << i << '\n';
}
void bar(const std::string& str) {
std::lock_guard<std::mutex> lk(m);
std::cout << str << '\n';
}
int operator()(int i) {
std::lock_guard<std::mutex> lk(m);
std::cout << i << '\n';
return i + 10;
}}; template <typename RandomIt>int parallel_sum(RandomIt beg, RandomIt end){
auto len = end - beg;
if (len < 1000)
return std::accumulate(beg, end, 0); RandomIt mid = beg + len/2;
auto handle = std::async(std::launch::async,
parallel_sum<RandomIt>, mid, end);
int sum = parallel_sum(beg, mid);
return sum + handle.get();
} int main(){
std::vector<int> v(10000, 1);
std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n'; X x;
// 以默认策略调用 x.foo(42, "Hello") :
// 可能同时打印 "Hello 42" 或延迟执行
auto a1 = std::async(&X::foo, &x, 42, "Hello");
// 以 deferred 策略调用 x.bar("world!")
// 调用 a2.get() 或 a2.wait() 时打印 "world!"
auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
// 以 async 策略调用 X()(43) :
// 同时打印 "43"
auto a3 = std::async(std::launch::async, X(), 43);
a2.wait(); // 打印 "world!"
std::cout << a3.get() << '\n'; // 打印 "53"
} // 若 a1 在此点未完成,则 a1 的析构函数在此打印 "Hello 42"

如果需要深入了解 std::async,可以参阅:std::async的使用总结

需要注意的地方,

当 std::async 使用默认参数启动时,它是这两种策略的组合,本质上使行为不可预测。使用带有默认启动参数的 std:async 还存在一系列其他复杂情况(包括无法预测线程局部变量是否被正确访问,异步任务存在根本无法运行的风险),因为 .get( ) 或 .wait() 可能不会在所有等待未来状态准备就绪的代码路径和循环中被调用,因为 std::async 返回的未来可能以延迟状态开始。

因此,为避免所有这些复杂情况,始终使用 std::launch::async 启动参数启动 std::async。

错误的使用方法,

//run myFunction using default std::async policy
auto myFuture = std::async(myFunction);

正确的使用方法,

//run myFunction asynchronously
auto myFuture = std::async(std::launch::async, myFunction);

那么什么时候使用 std::async 或者 std::thread 呢?

当我们使用  std::async (使用异步启动策略)时,我们是在说:

“我想在单独的线程上完成这项工作”。

当我们使用  std::thread 时,我们是在说:

“我想在一个新线程上完成这项工作”。

细微的差别意味着 std::async 通常使用线程池实现。这意味着如果我们多次调用一个方法 std::async,该方法中的线程 ID 通常会重复,即 std::async 从池中将多个作业分配给同一组线程。然而 std::thread,它永远不会。

这种差异意味着 std::thread 可能比 std::async 更占资源。

当然,这不意味着 std::async 更具优势,

目前,std::async 对于相当简单的程序,它可能最适合处理非常长时间运行的计算或长时间运行的 IO,它不太适合更细粒度的工作负载。为此,使用 std::thread 或使用 Microsoft 的 PPL 或 Intel 的 TBB 之类的东西来滚动自己的线程池。

参考:

std::async 与 std::thread 在什么时候使用比较合适的更多相关文章

  1. C++11 使用异步编程std::async和std::future

    先说明一点:std::asyanc是std::future的高级封装, 一般我们不会直接使用std::futrue,而是使用对std::future的高级封装std::async. 下面分别说一下. ...

  2. C++并发高级接口:std::async和std::future

    std::async和std::future std::async创建一个后台线程执行传递的任务,这个任务只要是callable object均可,然后返回一个std::future.future储存 ...

  3. c++ 如何获取多线程的返回值?(std::thread ,std::async)

    //简单的 c++11 线程,简单方便,成员函数随便调用,非成员函数也一样,如需要获取返回时,请自行使用条件变量 std::thread run([&](){ //执行一些耗时的操作 retu ...

  4. The promises and challenges of std::async task-based parallelism in C++11 C++11 std::async/future/promise

    转载 http://eli.thegreenplace.net/2016/the-promises-and-challenges-of-stdasync-task-based-parallelism- ...

  5. 第26课 std::async异步任务

    一. std::async函数模板 (一)std::async和std::thread的区别 1. 两者最明显的区别在于async采用默认启动策略时并不一定创建新的线程.如果系统资源紧张,那么std: ...

  6. 【C++并发实战】(三) std::future和std::promise

    std::future和std::promise std::future std::future期待一个返回,从一个异步调用的角度来说,future更像是执行函数的返回值,C++标准库使用std::f ...

  7. C++11之std::future和std::promise

    为什么C++11引入std::future和std::promise?C++11创建了线程以后,我们不能直接从thread.join()得到结果,必须定义一个变量,在线程执行时,对这个变量赋值,然后执 ...

  8. C++11之std::future和std::promise和std::std::packaged_task

    为什么C++11引入std::future和std::promise?C++11创建了线程以后,我们不能直接从thread.join()得到结果,必须定义一个变量,在线程执行时,对这个变量赋值,然后执 ...

  9. 用C++11的std::async代替线程的创建

    c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比 ...

  10. C++11 使用 std::async创建异步程序

    c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比 ...

随机推荐

  1. [转帖]TIDB TIKV 数据是怎么写入与通过Region 分割的?

    https://cloud.tencent.com/developer/article/1882194 国产的分布式数据库不少,TDSQL, OB, TIDB ,等等都是比较知名的产品,使用的分布式协 ...

  2. [转帖]ck的离线安装

    下载 下载地址: https://repo.clickhouse.tech/tgz/stable/ 下载的包: clickhouse-common-static clickhouse-server c ...

  3. [转帖]如何利用wrarp测试oss性能?

    https://zhuanlan.zhihu.com/p/529735003   前言 我们利用mino与ceph rgw搭建好的oss经过多层网络转发,传输速度必定有所折损,这个时候我们使用wrap ...

  4. [转帖]一文读懂 K8s 持久化存储流程

    https://zhuanlan.zhihu.com/p/128552232 作者 | 孙志恒(惠志) 阿里巴巴开发工程师 导读:众所周知,K8s 的持久化存储(Persistent Storage) ...

  5. [转帖]JMeter设置Http代理对web或者app进行录制

    https://www.cnblogs.com/jingdenghuakai/p/11125846.html 一.录制web 1.首先保证JMeter的安装环境都正确.启动JMeter:在安装路径的b ...

  6. [转帖]Tomcat maxKeepAliveRequests

    https://www.cnblogs.com/turn2i/p/10480088.html 在写这个问题前,其实我是为了分析项目碰到的一个tcp close wait问题.这个问题就不在这里讲了. ...

  7. Ergonomics JVM 的一种FullGC的说明

    https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/ergonomics.html 2 Ergonomics Ergo ...

  8. Redisson/Jedis 线程数不足报错问题的思考

    Redisson/Jedis 线程数不足报错问题的思考 背景 最近公司内总出现 Redis相关的错误 !-_-! 看我最近发的博客就可以看的出来. 这个错误提示其实是 两年前 清明节进行 压测时发现的 ...

  9. [转帖]yum 下载全量依赖 rpm 包及离线安装(终极解决方案)

    简介 通常生产环境由于安全原因都无法访问互联网.此时就需要进行离线安装,主要有两种方式:源码编译.rpm包安装.源码编译耗费时间长且缺乏编译环境,所以一般都选择使用离线 rpm 包安装. 验证环境 C ...

  10. Docker搭建SvnServer

    下载svn-server官方镜像 docker pull garethflowers/svn-server 运行svn-server容器 docker run -v /home/svn:/var/op ...