通俗讲解c++ future/shared_future

目录

  • future介绍
    • std::future对象只有在有效的(valid)情况下才有用(useful)
    • 查询future对象是否有效
    • future的拷贝构造函数是被禁用的
  • 对future的处理
    • 四种方式
    • 获取future的状态
      • future_status的三种状态:
  • shared_future介绍
    • future转换为shared_future
    • 对shared_future进行处理
  • future_error

future介绍

future 是一个用来获取异步任务的结果,其存在的意义
其实就是为了解决 std::thread 无法返回值的问题

future可以想象成一个对未来的预言,定义它的时候,相当于某个预言家预言在未来的某一天会出现这个事件。

  • 预言只有一个,但预言家们倒是有很多个(即获得future的方式)
    1.std::async 函数会返回一个std::future
    2.std::promise::get_future 调用成员函数,获取 std::future
    3.std::packaged_task::get_future 调用成员函数,获取 std::future

std::future对象只有在有效的(valid)情况下才有用(useful)

  • 既然是一个预言,那肯定要有内容,说明这个事件出现后会带来什么
    如果不说明白,只告诉一个名称,那就只有靠猜了,就没什么意义

所以std::future 默认构造函数创建的 future 对象不是有效的,没有什么意义。

但如果把这个名称和其他有意义的故事连在一起,那就有意义了
比如有一个叫Tom的人会协助英雄拯救世界,那Tom这个名称就有价值了

即用一个有效的 future 对象 move 赋值给 当前非有效的 future 对象 )

// 由默认构造函数创建的 std::future 对象,
// 初始化时该 std::future 对象处于为 invalid 状态.
std::future<int> foo, bar;
foo = std::async(do_get_value); // move 赋值, foo 变为 valid.
bar = std::move(foo); // move 赋值, bar 变为 valid, 而 move 赋值以后 foo 变为 invalid.

查询future对象是否有效

if (forecast.valid())
        std::cout << "forecast's value: " << forecast.get() << '\n';
    else
        std::cout << "forecast is not valid\n";

future的拷贝构造函数是被禁用的

std::future 的拷贝构造函数是被禁用的,只提供了默认的构造函数和 move 构造函数(注:C++ 新特新)。另外,std::future 的普通赋值操作也被禁用,只提供了 move 赋值操作。

预言这种东西,是没有办法复制的。

对future的处理

比如预言家说,世界要毁灭了,世界上可能就会有四种不同态度的人

四种方式

询问获取future结果有四种方式:get、wait、wait_until、wait_for

  1. get 等待异步操作结束并返回结果
  2. wait 只是等待异步操作完成,没有返回值
  3. wait_until 与wait类似,但可以设置一个绝对时间点
  4. wait_for 是超时等待返回结果

一个使用了wait()和get()的例子

#include                 // std::cout
#include                 // std::async, std::future
#include                 // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 为了体现效果, 该函数故意没有优化.
{
    for (int i = 2; i < x; ++i)
        if (x % i == 0)
            return false;
    return true;
}

int main()
{
    // call function asynchronously:
    std::future < bool > fut = std::async(do_check_prime, 194232491);

    std::cout << "Checking...\n";
    fut.wait();

    std::cout << "\n194232491 ";
    if (fut.get()) // guaranteed to be ready (and not block) after wait returns
        std::cout << "is prime.\n";
    else
        std::cout << "is not prime.\n";

    return 0;
}
  • get代表的应该是比较乐观的一类,等待预言到来,好好观赏那最后的一天。
    (等待线程结束,获取返回值)
  • wait 代表的比较悲观,什么预言都不管了,最后那天什么样都没兴趣。
    (等待线程结束,没有返回值)
  • wait_until 比较奇怪,一开始都和wait()一样,但不同的给了自己一个时间,到了那个时间,就变成wait_for 去问一下预言家发生了吗,并返回状态。
    (在指定时间内等待,时间到了,如果共享状态还不ready,就返回状态)
  • wait_for 就是比较正常的人,跑去问预言家预言还有多久会发生
    (获取future的状态)
// wait_until 使用
#include 
#include 
#include 
#include 
 
int main()
{
    std::chrono::system_clock::time_point two_seconds_passed
        = std::chrono::system_clock::now() + std::chrono::seconds(2);
 
    // 做出花 1 秒完成的 future
    std::promise<int> p1;
    std::future<int> f_completes = p1.get_future();
    std::thread([](std::promise<int> p1)
                { 
                    std::this_thread::sleep_for(std::chrono::seconds(1)); 
                    p1.set_value_at_thread_exit(9); 
                }, 
                std::move(p1)
    ).detach();
 
    // 做出花 5 秒完成的 future
    std::promise<int> p2;
    std::future<int> f_times_out = p2.get_future();
    std::thread([](std::promise<int> p2)
                { 
                    std::this_thread::sleep_for(std::chrono::seconds(5)); 
                    p2.set_value_at_thread_exit(8); 
                }, 
                std::move(p2)
    ).detach();
 
    std::cout << "Waiting for 2 seconds..." << std::endl;
 
    if(std::future_status::ready == f_completes.wait_until(two_seconds_passed))
        { std::cout << "f_completes: " << f_completes.get() << "\n"; }
    else
        { std::cout << "f_completes did not complete!\n"; }
 
    if(std::future_status::ready == f_times_out.wait_until(two_seconds_passed))
        { std::cout << "f_times_out: " << f_times_out.get() << "\n"; }
    else
        { std::cout << "f_times_out did not complete!\n"; }
 
    std::cout << "Done!\n";
}

/*
输出
Waiting for 2 seconds...
f_completes: 9
f_times_out did not complete!
Done!
*/

但只要预言成真了,std::future 对象相关联的共享状态的标志就会变为 ready

获取future的状态

就要查询future的状态,这里要使用的是wait_for

future_status的三种状态:

  • deferred:异步操作还没开始(还没到时间)
  • ready:异步操作已经完成(就是现在)
  • timeout:异步操作超时(预言家是个骗子)

但在一个有效的future对象上调用get会阻塞当前的调用者,
直到 Provider 设置了共享状态的值或异常(此时共享状态的标志变为 ready)

下面就是一个不断循环询问状态的过程

std::future_status status;
do
{
    // 我们对预言的实现的时限忍耐是很有限的,我们只能忍一秒
    status = fu.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);

shared_future介绍

shared_future 提供了一种访问异步操作结果的机制,
允许多个线程等待同一个共享状态,既支持移动操作也支持拷贝操作,
可以引用相同的共享状态,允许一旦共享状态就绪就可以多次检索共享状态下的值

future是一个只能讲给一个人听的秘密预言,而且阅后即毁。

所以future只能调用一次get,如果已经get过,其共享状态就会被释放,再次使用就会报错。

但如果想告诉更多的人都听到这个预言,就要用到shared_future

shared_future 在get后,不会释放共享状态,可以拷贝,共享某个共享状态的最终结果

future转换为shared_future

shared_future 可以通过 future 对象隐式转换
也可以通过显式调用std::future::share显式转换

注意:当future转换为shared_future后,原future对象就会变的无效。

// 隐式转换
// 将std::shared_future调整为std::future也是正确的
std::shared_future<int> f1 = std::async(std::launch::async, []() { return fib(20); });

// 显式转换
// 通过std::future移动构造std::shared_future对象
std::promise<void> ready_promise;
std::shared_future<void> ready_future(ready_promise.get_future());

// 从 promise 获取 future,并赋值给shared_future
promise<int> prom;
shared_future<int> sf1 = std::move(prom.get_future());

// 调用std::future::share显式转换
std::future<int> fut = std::async(do_get_value);
std::shared_future<int> shared_fut = fut.share();

对shared_future进行处理

和future是一样的,还是只有四种方式get,wait,wait_for,wait_until

shared_future和future的差别就体现在shared 分享,其他都是一样的

future_error

future error类定义了一个异常对象,
当线程库中处理异步执行和共享状态的函数失败时抛出该异常对象

你可能会见过这样一个错误

terminate called after throwing an instance of 'std::future_error'
  what():  std::future_error: Promise already satisfied

举一些会报错的例子就准备结束了,下面这些例子来自这篇好文
同时加了一些自己的注释

// 当future状态不是ready时抛错
#include 
#include 
 
int main()
{
    std::future<int> empty;
    try {
    // 只做出了预言,但预言的内容没有说出来
        int n = empty.get(); // The behavior is undefined, but
                             // some implementations throw std::future_error
    } catch (const std::future_error& e) {
        std::cout << "Caught a future_error with code \"" << e.code()
                  << "\"\nMessage: \"" << e.what() << "\"\n";
    }
}
// 两次调用 set_value 这个可以看我另一篇博文《通俗讲解c++ promise》
void test(std::promise<int>& para){
    para.set_value(10);
    para.set_value(20);
    return;
}

int main(){
    std::promise<int> pro;
    std::future<int> T = pro.get_future();
    std::thread fun(test, std::ref(pro));
    fun.join();
    cout << T.get() << endl;
    return 0;
}

检测future是否是引用共享状态
这个例子只发生在 future 第一次调用get() 或者 share() 之前,
没有调用"默认构造函数"或者"移动构造函数"

预言如果没有内容,就无法用任何语言对它进行描述

除了析构、赋值、valid函数之外,在没有指向共享状态的future中,调用其他任何成员函数,该行为都被定义为无效行为(在实际使用中鼓励抛出异常,从而表明当前状态不满足)

// 
void test(std::promise<int>& para){
    para.set_value(10);
    //para.set_value(20);
    return;
}

int main(){
    std::promise<int> pro;
    std::future<int> T = pro.get_future();
    std::thread fun(test, std::ref(pro));
    fun.join();
    cout << T.get() << endl;
    // 在调用get()后,共享状态就已经解除了,这时的get_future就是无效行为
    auto Error_T = pro.get_future();
    return 0;
}

/* 输出
10
terminate called after throwing an instance of 'std::future_error'
  what():  std::future_error: Future already retrieved
[1]    14518 abort      ./a.out
*/

参考

  • https://www.cnblogs.com/haippy/p/3280643.html
  • https://blog.csdn.net/weixin_36953500/article/details/90342603?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.edu_weight&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.edu_weight
  • https://blog.csdn.net/fengbingchun/article/details/104118831
  • https://www.yar2001.com/cpp/reference/zh/cpp/thread/future/wait_until.html
  • https://blog.csdn.net/weixin_43705457/article/details/104066817

如有错误,还请指正

你可能感兴趣的:(笔记,c++)