std::async 与 std::thread 在什么时候使用比较合适
前提:
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 在什么时候使用比较合适的更多相关文章
- C++11 使用异步编程std::async和std::future
先说明一点:std::asyanc是std::future的高级封装, 一般我们不会直接使用std::futrue,而是使用对std::future的高级封装std::async. 下面分别说一下. ...
- C++并发高级接口:std::async和std::future
std::async和std::future std::async创建一个后台线程执行传递的任务,这个任务只要是callable object均可,然后返回一个std::future.future储存 ...
- c++ 如何获取多线程的返回值?(std::thread ,std::async)
//简单的 c++11 线程,简单方便,成员函数随便调用,非成员函数也一样,如需要获取返回时,请自行使用条件变量 std::thread run([&](){ //执行一些耗时的操作 retu ...
- 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- ...
- 第26课 std::async异步任务
一. std::async函数模板 (一)std::async和std::thread的区别 1. 两者最明显的区别在于async采用默认启动策略时并不一定创建新的线程.如果系统资源紧张,那么std: ...
- 【C++并发实战】(三) std::future和std::promise
std::future和std::promise std::future std::future期待一个返回,从一个异步调用的角度来说,future更像是执行函数的返回值,C++标准库使用std::f ...
- C++11之std::future和std::promise
为什么C++11引入std::future和std::promise?C++11创建了线程以后,我们不能直接从thread.join()得到结果,必须定义一个变量,在线程执行时,对这个变量赋值,然后执 ...
- C++11之std::future和std::promise和std::std::packaged_task
为什么C++11引入std::future和std::promise?C++11创建了线程以后,我们不能直接从thread.join()得到结果,必须定义一个变量,在线程执行时,对这个变量赋值,然后执 ...
- 用C++11的std::async代替线程的创建
c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比 ...
- C++11 使用 std::async创建异步程序
c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比 ...
随机推荐
- [转帖]Redis压力测试——redis-benchmark
liunx 安装 redis & redis benchmark 1.下载安装包 点击官网,下载stable版本 wget http://download.redis.io/releases/ ...
- [转帖]Spring Cloud Alibaba Nacos 注册中心使用教程
一. 什么是Nacos Nacos是一个更易于构建云原生应用的动态服务发现(Nacos Discovery ).服务配置(Nacos Config)和服务管理平台,集注册中心+配置中心+服务管理于一身 ...
- Redis-rdb-tools与rdr工具学习与使用
Redis-rdb-tools与rdr工具学习与使用 简要说明 rdb工具是python写的一套工具,可以分析dump文件,获取key等信息. rdb其实有一套rdb-profiler工具, 能够导出 ...
- ant-design-vue 表单验证详解
表单验证详解 <template> <!-- 第一个坑 :model="formState.youForm" 一定要写成这样 不要写成:model="f ...
- css hover频繁闪烁
今天遇见一个问题. 在鼠标放上 图片上的时候. 删除图标一直不停的闪烁. 我当时觉得很奇怪,父子关系的结构 不应该闪烁呀. 看了下html和css,发现子元素(要hover)的元素是绝对定位了的 于是 ...
- KPlayer无人直播
KPlayer文档 其实就看这个教程就可以了: KPlayer文档 启动阿里云或者腾讯云的服务器进行这个步骤 服务器的购买链接: 腾讯云618 夏日盛惠_腾讯云年中优惠活动-腾讯云 域名特惠活动_域名 ...
- PE格式:新建节并插入代码
经过了前一章的学习相信你已经能够独立完成FOA与VA之间的互转了,接下来我们将实现在程序中插入新节区,并向新节区内插入一段能够反向连接的ShellCode代码,并保证插入后门的程序依旧能够正常运行不被 ...
- Pdfium.Net.Free 一个免费的Pdfium的 .net包装器--添加文本
项目地址: Pdfium.Net:https://github.com/1000374/Pdfium.Net PdfiumViewer:https://github.com/1000374/Pdfiu ...
- Netty-介绍-1
Netty介绍和应用场景 要求 已经掌握了 主要技术构成: Java OOP 编程. Java 多线程编程. Java IO 编程 . Java 网络编程. 常用的Java 设计模式(比如 观察者模式 ...
- Spring一套全通2—工厂高级
百知教育 - Spring系列课程 - 工厂高级特性 第十章.对象的生命周期 1. 什么是对象的生命周期 指的是一个对象创建.存活.消亡的一个完整过程 2. 为什么要学习对象的生命周期 由Spring ...