前提:

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. [转帖]Docker与k8s的恩怨情仇(四):云原生时代的闭源落幕

    https://zhuanlan.zhihu.com/p/388840887 在本系列前几篇文章中,我们介绍了从Cloud Foundry到Docker等PaaS平台的发展迭代过程.今天我们继续来为大 ...

  2. [转帖]SQL SERVER中隐式转换的一些细节浅析

    https://www.cnblogs.com/kerrycode/p/5853257.html 其实这是一篇没有技术含量的文章,精通SQL优化的请绕道.这个缘起于在优化一个SQL过程中,同事问了我一 ...

  3. [转帖]TiDB Control 使用说明

    https://docs.pingcap.com/zh/tidb/stable/tidb-control TiDB Control 是 TiDB 的命令行工具,用于获取 TiDB 状态信息,多用于调试 ...

  4. [转帖]【压测】通过Jemeter进行压力测试(超详细)

    文章目录 背景 一.前言 二.关于JMeter 三.准备工作 四.创建测试 4.1.创建线程组 4.2.配置元件 4.3.构造HTTP请求 4.4.添加HTTP请求头 4.5.添加断言 4.6.添加察 ...

  5. 【转帖】nginx变量使用方法详解-7

    https://www.diewufeiyang.com/post/581.html   在 (一) 中我们提到过,Nginx 变量的值只有一种类型,那就是字符串,但是变量也有可能压根就不存在有意义的 ...

  6. [转帖]DD硬盘性能相关因素

    https://www.jianshu.com/p/a15d7a65c876 本文简单介绍下DD测试硬盘性能时,各个因素的影响 首先列出测试结果   image.png oflag分析--/home ...

  7. [转帖]springcloud nacos配置

    配置文件中的nacos配置,discovery和config配置项 版本: <spring.boot.version>2.3.2.RELEASE</spring.boot.versi ...

  8. Kernel 内核支持的方法查询

    今天同事说自己的一个项目出现了报错如图: 报错的机器是 Windows XP 想找一个 windows XP的机器验证一下: 然后 想通过百度搜索确认一下 这个问题 但是发现基本上效果不大 改用了bi ...

  9. Opentelemetry Metrics API

    Opentelemetry Metrics API 目录 Opentelemetry Metrics API 概览 在没有安装SDK情况下的API行为 Measurements Metric Inst ...

  10. SMFL 教程&个人笔记(2)

    本文大部分来自官方教程的Google翻译 但是加了一点点个人的理解和其他相关知识 转载请注明 原文链接 :https://www.cnblogs.com/Multya/p/16317401.html ...