前提:

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. [转帖]Oracle中有大量的sniped会话

    https://www.cnblogs.com/abclife/p/15699959.html 1 2 3 4 5 6 7 SQL> select status ,count(*) from g ...

  2. [转帖]ramfs、tmpfs、rootfs、ramdisk介绍

    ramfs.tmpfs.rootfs.ramdisk介绍   bootleader--->kernel---->initrd(是xz.cpio.是ramfs的一种,主要是驱动和为了加载ro ...

  3. 【转帖】Mysql一张表可以存储多少数据

    https://www.cnblogs.com/wenbochang/p/16723537.html Mysql一张表可以存储多少数据 在操作系统中,我们知道为了跟磁盘交互,内存也是分页的,一页大小4 ...

  4. [转帖]解读内核 sysctl 配置中 panic、oops 相关项目

    写在前面 本篇文章的内容主要来自内核源码树 Documentation/admin-guide/sysctl/kernel.rst文件. softlockup vs hardlockup softlo ...

  5. CentOS7上面一键部署rabbitmq的简单方法

    1. rabbitmq的安装部署比redis之类的要麻烦一些. 主要是他是基于erlang写的 而不是基于c或者是c++写的 2. 很多时候编译需要添加很多组件, 但是一些机器可能不会让添加这么多的组 ...

  6. 解决刷新SwaggerUi控制台报错

    一.问题描述 在浏览器刷新SwaggerUI的页面,控制台就报错: java.lang.NumberFormatException: For input string: "" at ...

  7. echarts设置标题样式

    <!DOCTYPE html> <html> <!-- https://blog.csdn.net/weixin_42698255/article/details/892 ...

  8. ClickHouse(14)ClickHouse合并树MergeTree家族表引擎之VersionedCollapsingMergeTree详细解析

    目录 建表语法 使用场景 合并算法 使用例子. 资料分享 参考文章 VersionedCollapsingMergeTree引擎继承自MergeTree并将折叠行的逻辑添加到合并数据部分的算法中.Ve ...

  9. 微服务用yml安装系统(第一版)

    当用微服务安装系统后,面临服务较多,一个一个安装比较麻烦,是否有统一的脚本可以直接执行安装呢?答案是肯定的: 1.首先介绍一下所有安装脚本,如下图 spd-volume:是各服务外挂的资料卷 comm ...

  10. python.exe和pythonw.exe的区别(区分.py、.pyw、.pyc、.pyo文件)

    python和pythonw 在Windows系统搭建好Python的环境后,进入Python的安装目录,大家会发现目录中有python.exe和pythonw.exe两个程序.如下图所示: 它们到底 ...