什么是std :: promise?

我非常熟悉C ++ 11的std::threadstd::asyncstd::future组件(例如参见这个答案),这很简单。

然而,我不能完全理解std::promise是什么,它做了什么以及在哪种情况下最好使用它。 标准文档本身并不包含超过其类纲要的大量信息,也不仅仅是:: thread。

有人能否给出一个简短的简洁例子,说明需要std::promise情况,以及哪里是最常用的解决方案?


用[futures.state]的话来说, std::future是一个异步返回对象(“一个读取共享状态结果的对象”),而std::promise是一个异步提供者(“提供结果的对象到一个共享的状态“),即承诺是你设定结果的事情,以便你可以从相关的未来中获得它。

异步提供程序是最初创建未来引用的共享状态的内容。 std::promise是一种异步提供程序, std::packaged_task是另一种, std::async的内部细节是另一种。 每个人都可以创建一个共享状态,并为您提供一个共享该状态的std::future ,并且可以使状态准备就绪。

std::async是一个更高级的便利实用程序,它为您提供了异步结果对象,并在内部负责创建异步提供程序,并在任务完成时使共享状态准备就绪。 你可以使用std::packaged_task (或std::bindstd::promise )和std::thread来模拟它,但使用std::async更安全和更简单。

std::promise有点低级,因为当你想将异步结果传递给未来时,但是使结果准备好的代码不能包含在适合传递给std::async的单个函数中。 例如,您可能有几个promise和相关future的数组,并有一个单独的线程进行多次计算并在每个承诺上设置结果。 async只允许你返回一个结果,返回几个你需要多次调用async ,这可能会浪费资源。


我现在了解情况会好一些(因为这里的答案,这里的情况不会很少)!所以我想我自己写一点小文章。


在C ++ 11中有两个截然不同但相关的概念:异步计算(一个被称为别处的函数)和并发执行(一个线程,可以同时工作)。 这两者有点正交的概念。 异步计算只是函数调用的不同风格,而线程则是执行上下文。 线程本身是有用的,但为了讨论的目的,我将把它们作为实现细节。


异步计算有一个抽象层次。 举例来说,假设我们有一个函数需要一些参数:

int foo(double, char, bool);

首先,我们有模板std::future<T> ,它表示类型T的未来值。 该值可以通过成员函数get()来获取,该函数通过等待结果来有效地同步程序。 或者,未来支持wait_for() ,可用于探测结果是否已经可用。 期货应该被认为是普通回报类型的异步投入替代品。 对于我们的示例函数,我们期望std::future<int>

现在,从最高层到最低层的层次结构:

  • std::async :执行异步计算的最方便和直接的方法是通过async函数模板,该模板立即返回匹配的未来:

    auto fut = std::async(foo, 1.5, 'x', false);  // is a std::future<int>
    

    我们很少控制细节。 特别是,我们甚至不知道该函数是在并发执行时,在get()或其他一些黑魔法时连续执行的。 但是,如果需要,结果很容易获得:

    auto res = fut.get();  // is an int
    
  • 现在我们可以考虑如何实现像async一样的东西,但是以我们控制的方式。 例如,我们可能会坚持要在一个单独的线程中执行该函数。 我们已经知道我们可以通过std::thread类提供一个单独的线程。

    下一个较低的抽象层次就是: std::packaged_task 。 这是一个封装函数并为函数返回值提供未来的模板,但对象本身是可调用的,调用它是由用户决定的。 我们可以像这样设置它:

    std::packaged_task<int(double, char, bool)> tsk(foo);
    
    auto fut = tsk.get_future();    // is a std::future<int>
    

    一旦我们呼叫任务并且呼叫完成,未来就会变得准备就绪。 这是单独线程的理想工作。 我们必须确保将任务移至线程中:

    std::thread thr(std::move(tsk), 1.5, 'x', false);
    

    线程立即开始运行。 我们可以detach它,或者在范围的末尾join它,或者在任何时候(例如使用Anthony Williams的scoped_thread包装器,它应该在标准库中)。 尽管使用std::thread的细节并不在我们这里。 只要确定最终加入或脱离thr 。 重要的是,只要函数调用完成,我们的结果就准备好了:

    auto res = fut.get();  // as before
    
  • 现在我们已经降到最低级别了:我们将如何实施打包任务? 这就是std::promise的地方。诺言是与未来交流的基石。 主要步骤如下:

  • 调用线程作出承诺。

  • 调用线程从承诺中获得未来。

  • 承诺和函数参数一起被移入一个单独的线程。

  • 新线程执行该功能并填充履行承诺。

  • 原始线程检索结果。

  • 举个例子,这是我们自己的“打包任务”:

    template <typename> class my_task;
    
    template <typename R, typename ...Args>
    class my_task<R(Args...)>
    {
        std::function<R(Args...)> fn;
        std::promise<R> pr;             // the promise of the result
    public:
        template <typename ...Ts>
        explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { }
    
        template <typename ...Ts>
        void operator()(Ts &&... ts)
        {
            pr.set_value(fn(std::forward<Ts>(ts)...));  // fulfill the promise
        }
    
        std::future<R> get_future() { return pr.get_future(); }
    
        // disable copy, default move
    };
    

    这个模板的用法与std::packaged_task基本相同。 请注意,移动整个任务包括移动诺言。 在更特殊的情况下,还可以将一个promise对象明确地移动到新线程中,并将其作为线程函数的一个函数参数,但是像上面那样的任务包装似乎是一种更灵活和更少侵入的解决方案。


    例外

    承诺与例外密切相关。 单单一个承诺的接口不足以完全传达它的状态,所以只要承诺上的操作没有意义,就会抛出异常。 所有的异常类型都是std::future_error ,它来源于std::logic_error 。 首先,描述一些限制条件:

  • 默认构建的承诺是无效的。 不积极的承诺可能会毫无后果地死去。

  • 当未来通过get_future()获得时,承诺变得活跃。 但是,只能获得一个未来!

  • 一个承诺必须通过set_value()来满足,或者在它的生命周期结束之前通过set_exception()设置一个异常,如果它的未来将被消耗。 一个满意的承诺可以毫无后果地死去,并且get()在未来可用。 具有例外的承诺将在未来调用get()时引发存储的异常。 如果诺言既没有价值也没有例外,在未来调用get()会引发一个“破坏的诺言”例外。

  • 这里有一些测试系列来演示这些各种特殊的行为。 首先,线束:

    #include <iostream>
    #include <future>
    #include <exception>
    #include <stdexcept>
    
    int test();
    
    int main()
    {
        try
        {
            return test();
        }
        catch (std::future_error const & e)
        {
            std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl;
        }
        catch (std::exception const & e)
        {
            std::cout << "Standard exception: " << e.what() << std::endl;
        }
        catch (...)
        {
            std::cout << "Unknown exception." << std::endl;
        }
    }
    

    现在进行测试。

    案例1:无效的承诺

    int test()
    {
        std::promise<int> pr;
        return 0;
    }
    // fine, no problems
    

    案例2:主动承诺,未使用

    int test()
    {
        std::promise<int> pr;
        auto fut = pr.get_future();
        return 0;
    }
    // fine, no problems; fut.get() would block indefinitely
    

    案例3:期货太多

    int test()
    {
        std::promise<int> pr;
        auto fut1 = pr.get_future();
        auto fut2 = pr.get_future();  //   Error: "Future already retrieved"
        return 0;
    }
    

    案例4:满意的承诺

    int test()
    {
        std::promise<int> pr;
        auto fut = pr.get_future();
    
        {
            std::promise<int> pr2(std::move(pr));
            pr2.set_value(10);
        }
    
        return fut.get();
    }
    // Fine, returns "10".
    

    案例5:太满意了

    int test()
    {
        std::promise<int> pr;
        auto fut = pr.get_future();
    
        {
            std::promise<int> pr2(std::move(pr));
            pr2.set_value(10);
            pr2.set_value(10);  // Error: "Promise already satisfied"
        }
    
        return fut.get();
    }
    

    如果有多个set_valueset_exception则会抛出同样的异常。

    案例6:例外

    int test()
    {
        std::promise<int> pr;
        auto fut = pr.get_future();
    
        {
            std::promise<int> pr2(std::move(pr));
            pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo")));
        }
    
        return fut.get();
    }
    // throws the runtime_error exception
    

    案例7:失败的承诺

    int test()
    {
        std::promise<int> pr;
        auto fut = pr.get_future();
    
        {
            std::promise<int> pr2(std::move(pr));
        }   // Error: "broken promise"
    
        return fut.get();
    }
    

    Bartosz Milewski提供了一个很好的写法。

    C ++将期货的实施分解为一组小块

    std :: promise是这些部分之一。

    承诺是一种将执行函数的线程返回值(或异常)传递给未来函数的线程的工具。

    ...

    未来是在承诺通道的接收端周围构建的同步对象。

    所以,如果你想使用未来,你最终会得到一个用来获得异步处理结果的承诺。

    该页面的示例是:

    promise<int> intPromise;
    future<int> intFuture = intPromise.get_future();
    std::thread t(asyncFun, std::move(intPromise));
    // do some other stuff
    int result = intFuture.get(); // may throw MyException
    
    链接地址: http://www.djcxy.com/p/30739.html

    上一篇: What is std::promise?

    下一篇: What is std::move(), and when should it be used?