【转载】走进 C++11(二十七)处理未来发生的事情 std::future

552 阅读4分钟

原文地址:走进 C++11(二十七)处理未来发生的事情 std::future

原作者举的例子十分的形象、通俗易懂。我对文章的格式和错别字进行了调整,并在他的基础上把重点部分进一步解释完善。以下是正文。

正文

这一节可能是 C++11 最难说明白的一节。

其实 future 有两个兄弟,一个是 std::future , 一个是它大哥std::shared_future。它们的区别就是 std::future 只支持移动语义,它所引用的共享状态不与另一异步返回对象共享。换成人话就是如果你想再多个线程共享一个 future ,那么你应该用 std::shared_future ,换成大白话就是你要是想多个线程等待一个 future ,那么你应该用 std::shared_future 。如果还是不明白,文章最后会给一个小例子说明。这篇文章主要讲的是 std::future 。

同时讲 std::future 离不开它的承载者和创建者, 也就是 std::asyncstd::packaged_taskstd::promise,会在今后的文章一一描述。

首先我们先看 std::future 是如何定义的(如果感觉枯燥可略过这节):

std::future 类的定义

定义于头文件
template< class T > class future;(1)(C++11 起)
template< class T > class future<T&>;(2)(C++11 起)
template<> class future;(3)(C++11 起)

成员函数的定义

构造、销毁、赋值、共享状态
(构造函数)构造 future 对象 (公开成员函数)
(析构函数)析构 future 对象 (公开成员函数)
operator= 注意是仅可移动赋值的 future 对象 (公开成员函数)
share从 *this 转移共享状态给 shared_future 并返回它 (公开成员函数)
获取结果
get返回结果 (公开成员函数)
查询状态
valid检查 future 是否拥有共享状态 (公开成员函数)
wait等待结果变得可用 (公开成员函数)
wait_for等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回。(公开成员函数)
wait_until等待结果,如果在已经到达指定的时间点时仍然无法得到结果,则返回。(公开成员函数)

异步访问机制

类模板 std::future 提供访问异步操作结果的机制:

  • (通过 std::async 、 std::packaged_task 或 std::promise 创建的)异步操作能提供一个 std::future 对象给该异步操作的创建者

  • 然后,异步操作的创建者能用各种方法查询、等待或从 std::future 提取结果。若异步操作仍未提供值,则这些方法可能阻塞

  • 异步操作准备好发送结果创建者时,它能通过修改链接到创建者的 std::future 的共享状态(例如 std::promise::set_value )进行。

情景举例

std::future 涉及到了并行编程的概念。为了更好的理解,我们先讨论一个我们会经常遇到的场景。现在你要做三件事,一个是炖排骨一个是烧开水,同时你还在追最火的电视剧。那么作为一个正常人,你不会傻傻的先烧开水,等水烧完了炖排骨,等排骨好了再去看电视剧吧。一个正常人大概率会选择炖上排骨、烧上水后就去看电视剧,然后不断的查看排骨和水是不是好了

当然排骨和水的处理方式不一样,烧热水等开了会响,而排骨就要根据个人经验还有排骨当前的状态放入佐料并且控制时间。这就涉及到我们编程中的架构模型,std::future 要讨论的就是排骨这种情况,而不是烧水。

在编程的过程中,程序中往往会有一些功能或者很耗时或者很占计算资源的操作,这样的程序我们往往倾向于让它在另一个 thread 中运行,我们的主 thread 可以先干其他事情,等干完了其他事情在来看看之前的工作干完了没有,如果干完了,那么拿出结果,如果没干完就等它干完或者只等一会。

下边就举一个例子

实例代码

#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int main()
{
    std::future<int> future = std::async(std::launch::async, []()
                                         {
                                             std::this_thread::sleep_for(std::chrono::seconds(3)); ///< 线程睡眠 3 秒
                                             return 8;
                                         });

    std::cout << "开始炖排骨...\n";
    std::future_status status;
    do
    {
        // 然后在主线程中每秒检查一次排骨线程
        status = future.wait_for(std::chrono::seconds(1)); ///< 每次等待 1 秒
        if (status == std::future_status::deferred)
        {
            std::cout << "deferred\n"; ///< 线程延迟执行
        }
        else if (status == std::future_status::timeout)
        {
            std::cout << "一秒以后排骨还没好\n"; ///< 线程执行时间(3s)超过了等待时间(1s)
        }
        else if (status == std::future_status::ready)
        {
            std::cout << "排骨好了!\n"; ///< 在等待时间内线程执行完毕
        }
    } while (status != std::future_status::ready);

    std::cout << "result is " << future.get() << '\n';
}

执行结果为

image.png

当然我们也可以不用在主线程中隔一会看看排骨好没好,可以直接在干别的活的时候(其他线程)中直接检查排骨的状态,那么就要用到文章之前提到的 std::shared_future 了。

std::shared_future 的例子

类模板 std::shared_future 提供访问异步操作结果的机制,类似 std::future ,除了允许多个线程等候同一共享状态。不同于仅可移动的 std::future (故只有一个实例能指代任何特定的异步结果),std::shared_future 可复制而且多个 shared_future 对象指代同一共享状态

若每个线程通过其自身的 shared_future 对象副本访问,则从多个线程访问同一共享状态是安全的。

shared_future 不是文章的重点,这里仅仅举一个例子说明:

有了它,这样我们就不用在主线程里面检查排骨好了没,可以直接在其他线程(比如看剧)里直接检查,因为它们的状态是共享的

#include <iostream>
#include <future>
#include <chrono>

int main() {
    std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise;
    std::shared_future<void> ready_future(ready_promise.get_future());

    std::chrono::time_point<std::chrono::high_resolution_clock> start;

    auto fun1 = [&, ready_future]() -> std::chrono::duration<double, std::milli> {
        t1_ready_promise.set_value();
        ready_future.wait(); // 等待来自 main() 的信号
        return std::chrono::high_resolution_clock::now() - start;
    };


    auto fun2 = [&, ready_future]() -> std::chrono::duration<double, std::milli> {
        t2_ready_promise.set_value();
        ready_future.wait(); // 等待来自 main() 的信号
        return std::chrono::high_resolution_clock::now() - start;
    };

    auto result1 = std::async(std::launch::async, fun1);
    auto result2 = std::async(std::launch::async, fun2);

    // 等待线程变为就绪
    t1_ready_promise.get_future().wait();
    t2_ready_promise.get_future().wait();

    // 线程已就绪,开始时钟
    start = std::chrono::high_resolution_clock::now();

    // 向线程发信使之运行
    ready_promise.set_value();

    std::cout << "Thread 1 received the signal "
              << result1.get().count() << " ms after start\n"
              << "Thread 2 received the signal "
              << result2.get().count() << " ms after start\n";
}

执行结果为

image.png