首页 > 其他分享 >std::async

std::async

时间:2024-03-25 16:47:54浏览次数:13  
标签:std 异步 future 线程 async wait

C++11提供了异步接口std::async,通过这个异步接口可以很方便的获取线程函数的执行结果。std::async会自动创建一个线程去调用线程函数,它返回一个std::future,这个future中存储了线程函数返回的结果,当我们需要线程函数的结果时,直接从future中获取,非常方便。但是我想说的是,其实std::async给我们提供的便利可不仅仅是这一点,它首先解耦了线程的创建和执行,使得我们可以在需要的时候获取异步操作的结果;其次它还提供了线程的创建策略(比如可以通过延迟加载的方式去创建线程),使得我们可以以多种方式去创建线程。

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:延迟加载方式创建线程。调用async时不创建线程,直到调用了future的get或者wait时才创建线程(惰性求值)。

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

第二个参数是线程函数

线程函数可接受function, lambda expression, bind expression, or another function object等可调用对象。

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

返回值std::future

  • 指代此次调用 std::async 所创建的共享状态的 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"

可能的结果:

The sum is 10000
43
world!
53
Hello 42

深入理解线程创建策略

  • std::launch::async调度策略意味着函数必须异步执行,即在另一线程执行。
  • std::launch::deferred调度策略意味着函数可能只会在std::async返回的future对象调用get或wait时执行。那就是,执行会推迟到其中一个调用发生。当调用get或wait时,函数会同步执行,即调用者会阻塞直到函数运行结束。如果get或wait没有被调用,函数就绝对不会执行。

两者策略都很明确,然而该函数的默认策略却很有趣,它不是你显示指定的,也就是第一个函数原型中所用的策略即std::launch::async | std::launch::deferred,c++标准中给出的说明是:

进行异步执行还是惰性求值取决于实现

这种调度策略我们没有办法预知函数func是否会在哪个线程执行,甚至无法预知会不会被执行,因为func可能会被调度为推迟执行,即调用get或wait的时候执行,而get或wait是否会被执行或者在哪个线程执行都无法预知。

同时这种调度策略的灵活性还会混淆使用thread_local变量,这意味着如果func写或读这种线程本地存储(Thread Local Storage,TLS),预知取到哪个线程的本地变量是不可能的。

它也影响了基于wait循环中的超时情况,因为调度策略可能为deferred的,调用wait_for或者wait_until会返回值std::launch::deferred。这意味着下面的循环,看起来最终会停止,但是,实际上可能会一直运行:

void func()           // f睡眠1秒后返回
{
    std::this_thread::sleep_for(1);
}
auto future = std::async(func);      // (概念上)异步执行func
while(future.wait_for(100ms) !=         // 循环直到func执行结束
      std::future_status::ready)     // 但这可能永远不会发生
{
    ...
}

为避免陷入死循环,我们必须检查future是否把任务推迟,然而future无法获知任务是否被推迟,一个好的技巧就是通过wait_for(0)来获取future_status是否是deferred:

auto future = std::async(func);      // (概念上)异步执行f
if (fut.wait_for(0) == std::future_status::deferred)  // 如果任务被推迟
{
    ...     // fut使用get或wait来同步调用f
} else {            // 任务没有被推迟
    while(fut.wait_for(100ms) != std::future_status::ready) { // 不可能无限循环
      ...    // 任务没有被推迟也没有就绪,所以做一些并发的事情直到任务就绪
    }
    ...        // fut就绪
}

 

标签:std,异步,future,线程,async,wait
From: https://www.cnblogs.com/love-9/p/18094733

相关文章

  • std::unique_lock
    C++11标准中定义了另外一个与MutexRAII相关类unique_lock,该类与lock_guard类相似,也很方便线程对互斥量上锁,但它提供了更好的上锁和解锁控制。unique_lock对象以独占所有权的方式(uniqueowership)管理mutex对象的上锁和解锁操作,所谓独占所有权,就是没有其他的unique_loc......
  • std::lock_guard 介绍
    std::lock_gurad是C++11中定义的模板类。定义如下:template <class Mutex>class lock_guard;lock_guard对象通常用于管理某个锁(Lock)对象,因此与MutexRAII相关,方便线程对互斥量上锁,即在某个lock_guard对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而lo......
  • Python实战:异步I/O:asyncio事件循环
    1.引言在Python中,异步I/O是一种非阻塞的I/O操作方式。与传统的同步I/O操作不同,异步I/O允许程序在等待I/O操作完成的同时执行其他任务。asyncio是Python标准库中用于编写异步代码的库,它提供了一个事件循环来处理异步操作。事件循环是asyncio的核心组件,它负责调度和执行异步......
  • C# GUI_Async_await异步报告
    //.net8环境WinformnamespaceGUI_Async_await异步报告_供参考_{publicpartialclassForm1:Form{publicForm1(){InitializeComponent();}privatevoidbutton1_Click(objectsender,EventArgse)......
  • std::vector 和 std::list 区别
    std::vector和std::list区别?std::vector和std::list是C++标准库中两种不同的容器类型,它们之间有以下几个主要区别:存储结构:std::vector是连续内存空间上的动态数组,元素在内存中是连续存储的。std::list是基于双向链表实现的,元素在内存中是非连续存储的。......
  • 自己编译RustDesk,并将自建ID服务器和key信息写入客户端
    前言:搭建RustDesk编译环境    今天总算是把编译环境给折腾清楚了,编译出来了至少能用,但说不上好用,问题还不少,官方的客户端就是要手工填写ID服务器地址和key才可以用,而且还容易被别人白嫖你搭建的服务器,当然如果拿到你编译后的客户端,也是存在被白嫖的可能。这方面还没......
  • dremio AsyncStreamConf 简单说明
    AsyncStreamConf主要是关于异步以及cache配置属性的参数配置,dremio存储扩展不少都实现了此接口参考实现使用的地方整体使用 存储插件基本都会使用到,包含了一些reader,同时还有文件系统的包装处理 ceCacheFileSystemWrapper的使用 这个是dremioce包中的一个CacheF......
  • C#理解async和await
    1.async和await在C#中,async和await是用于处理异步操作的关键字。async:用于定义一个方法是异步的。当一个方法被声明为async时,它可以包含await表达式,并且其返回类型通常是Task或Task。await:用于暂停异步方法的执行,等待异步操作的完成。在使用await关键字时,其后面的表达......
  • [转]WPF 使用 Dispatcher 的 InvokeAsync 和 BeginInvoke 的异常处理差别
    一般认为WPF的Dispatcher的InvokeAsync方法是BeginInvoke方法的平替方法和升级版,接近在任何情况下都应该在业务层使用InvokeAsync方法代替BeginInvoke方法。然而在异常的处理上,这两个方法还是有细微的差别的,不能说是坏事,依然可以认为使用InvokeAsync方法代替BeginI......
  • 当@Async注解遇上Spring的循环依赖:一个故障排查之旅
    在Java后端开发中,Spring框架无疑是一个强大的助手,它以简单的方式帮助我们管理依赖项、配置和创建异步任务。然而,即使在这个成熟的框架中,也会有一些坑会让开发者头疼。今天,我们就来聊聊Spring中的一个常见问题——当@Async注解遇上循环依赖时会发生什么。问题的起源一位工......