# 扩展特性

promise和future形式的任务在C++11中的名声很微妙。一方面，它们比线程或条件变量更容易使用；另一方面，也有明显的不足——不能合成。C++20/23中弥补了这个缺陷。

我曾经以`std::async`、`std::packaged_task`或`std::promise`和`std::future`的形式，写过关于任务的文章。C++20/23中，我们可以使用加强版的future。

## 并发技术标准 v1

**std::future**

扩展future很容易解释。首先，扩展了C++11的`std::future`接口；其次，一些新功能可组合创建特殊的future。先从第一点开始说起：

扩展future有三种新特性:

* 展开构造函数，可用于展开已包装的外部future(`future<future<T>>`)。
* 如果共享状态可用，则返回谓词is\_ready。
* 添加了可延续附加到future的方法。

起初，future的状态可以是valid或ready。

**valid与ready**

* valid: 如果future具有共享状态(带有promise)，那么它就是有效的。这并不是必须的，因为可以默认构造一个没有promise的`std::future`。
* ready: 如果共享状态可用，future就已经准备好了。换句话说，如果promise已经完成，则future就已经准备好了。

因此，`(valid == true)`是`(ready == true)`的一个必要不充分条件。

我对promise和future的建模就是数据通道的两个端点。

![](/files/-M2nHSTl065hFKGjJimn)

现在，valid和ready的区别就非常自然了。如果有一个数据通道的promise，则future的状态是valid。如果promise已经将其结果放入数据通道中，则future的状态是ready。

现在，为了延迟future，我们来了解一下then。

**使用then的延迟**

then具有将一个future附加到另一个future的能力，这样一个future就能被另一个future所嵌套。展开构造函数的任务是对外部future进行展开的。

> **N3721提案**
>
> 迎来第一个代码段之前，必须介绍一下[N3721](https://isocpp.org/files/papers/N3721.pdf)提案。本节的大部分内容是关于“`std::future<T>`和相关API”的改进建议。奇怪的是，提案作者最初没有使用`get`获取future最后的结果。因此，我在示例添加了`res.get`，并将结果保存在变量`myResult`中，并修正了一些错别字。

```cpp
#include <future>
using namespace std;
int main() {

  future<int> f1 = async([]() {return123; });
  future<string> f2 = f1.then([](future<int> f) {
    return to_string(f.get()); // here .get() won't block
    });

  auto myResult = f2.get();

}
```

`to_string(f.get())`(第7行)和`f2.get()`(第10行)之间有细微的区别。正如我在代码片段中已经提到的：第一个调用是非阻塞/异步的，第二个调用是阻塞/同步的。`f2.get()`会一直等待，直到future链的结果可用。这种方法也适用于长链似的调用：`f1.then(…).then(…).then(…).then(…).then(…)`。最后，阻塞式调用`f2.get()`获取结果。

**std::async , std::packaged\_task和std::promise**

关于`std::async`、`std::package_task`和`std::promise`的扩展没有太多可说的。那为什么还要提一下，是因为在C++ 20/23中这三种扩展都会返回扩展了的future。

future的构成令人越来越兴奋了，现在我们可以组合异步任务了。

**创建新future**

C++20获得了四个用于创建新future的新函数。这些函数是`std::make_ready_future`、`std::make_execptional_future`、`std::when_all`和`std::when_any`。首先，让我们看看`std::make_ready_future`和`std::make_exceptional_future`。

**std::make\_ready\_future和std::make\_exceptional\_future**

这两个功能都立即创建了一个处于ready状态的future 。第一种情况下，future是有价值的；第二种情况下是出现了异常。一开始看起来很奇怪的事情，但细想却很有道理。C++11中，创建一个future需要promise。即使共享状态可用，这也是必要的。

使用make\_ready\_future创建future

```cpp
future<int> compute(int x) {
  if (x < 0) return make_ready_future<int>(-1);
  if (x == 0) return make_ready_future<int>(0);
  future<int> f1 = async([]() { return do_work(x); });
  return f1;
}
```

因此，如果(x > 0)保持不变，则只能通过promise来计算结果。

简短说明一下：这两个函数都是单子(monad)中返回的函数挂件。现在，让我们从future的合成开始说起。

**std::when\_any和std::when\_all**

这两种功能有很多共同之处。首先，来看看输入：

```cpp
template < class InputIt >
auto when_any(InputIt first, InputIt last)
    -> future<when_any_result<
        std::vector<typename std::iterator_traits<InputIt>::value_type>>>;

template < class... Futures >
auto when_any(Futures&&... futures)
    -> future<when_any_result<std::tuple<std::decay_t<Futures>...>>>;

template < class InputIt >
auto when_all(InputIt first, InputIt last)
    -> future<std::vector<typename std::iterator_traits<InputIt>::value_type>>;

template < class... Futures >
auto when_all(Futures&&... futures)
    -> future<std::tuple<std::decay_t<Futures>...>>;
```

这两个函数都接受一对关于future范围的迭代器，或任意数量的future迭代器。二者最大的区别是，在使用迭代器对的情况下，future必须是相同类型的；而对于任意数量的future，可以使用不同类型的future，甚至可以混用`std::future`和`std::shared_future`。

函数的输出，取决于是否使用了一对迭代器或任意数量的future(可变参数模板)。这两个函数都返回一个future。如果使用一对迭代器，将得到`std::vector`: `future<vector<future<R>>>`中的future。如果使用可变参数模板，会得到`std::tuple`: `future<tuple<future<R0>, future<R1>,…>>`。

已经了解了它们的共性。如果所有输入future(when\_all)或任何输入future(when\_any)都处于ready状态，那么这两个函数返回的future也就处于ready状态。

接下来的两个例子，会展示`std::when_all`和`std::when_any`的用法。

**std::when\_all**

Future的组合与`std::when_all`

```cpp
#include <future>

using namespace std;

int main() {

  shared_future<int> shared_future1 = async([] {return intResult(123); });
  future<string> future2 = async([]() {return stringResult("hi"); });

  future<tuple<shared_future<int>, future<string>>>all_f =
    when_all(shared_future1, future2);

  future<int> result = all_f.then(
    [](future<tuple<shared_future<int>, future<string>>> f) {
      return doWork(f.get());
    });

  auto myResult = result.get();
}
```

`future all_f`(第10行)由future的`shared_future1`(第7行)和`future2`(第8行)组成。如果所有future都准备好了，则执行第13行获取future的结果。本例中，将执行第15行中的`all_f`。结果保存在future中，可以在第18行进行获取。

**std::when\_any**

Future的组合与std::when\_any

```cpp
#include <future>
#include <vector>

using namespace std;

int main() {

  vector<future<int>> v{ ..... };
  auto future_any = when_any(v.begin(), v.end());

  when_any_result<vector<future<int>>> result = future_any.get();

  future<int>& read_future = result.futures[result.index];

  auto myResult = ready_future.get();
}
```

when\_any中的future可以在第11行中获取结果。`result`会提供已经准备就绪future的信息。如果不使用when\_any\_result，就没必要查询每个future是否处于ready状态了。

如果它的某个输入future处于ready状态，那么future\_any就处于ready状态。第11行中的`future_any.get()`会返回future的结果。通过使用`result.futures[result.index]`(第13行)，可以获取ready\_future，并且由于使用`ready_future.get()`，也可以对任务的结果进行查询。

如[P0701r1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0701r1.html)中描述，“它们没想象的那样通用、有表现力或强大”，其既不是标准化的future，也不是并发的[TS v1 future](http://en.cppreference.com/w/cpp/experimental/concurrency)。此外，执行者作为执行的基本构件，必须与新的future相统一。

## 统一的Future

标准化和并发TSv1的future有什么缺点吗?

**缺点**

上述文件(P0701r1)很好地说明了future的不足之处。

**future/promise不应该耦合到std::thread执行代理中**

C++11只有一个executor:`std::thread`。因此，future和`std::thread`是不可分割的。这种情况在C++17和STL的并行算法中得到了改变，新的executor中变化更大，并可以使用它来配置future。例如，future可以在单独的线程中运行，也可以在线程池中运行，或者只是串行运行。

**在哪里持续调用了.then ?**

下面的例子中，有一个简单的延续。

使用`std::future`的延续

```cpp
future<int> f1 = async([]() { return 123; });
future<string> f2 = f1.then([](future<int> f) {
    return to_string(f.get());
});
```

问题是：延续应该在哪里运行?有一些可能性:

1. 消费端：消费者执行代理总是执行延续。
2. 生产端：生产者执行代理总是执行延续。
3. inline\_executor语义：如果在设置延续时，共享状态已就绪，则使用者线程将执行该延续。如果在设置延续时，共享状态还没有准备好，则生产者线程将执行该延续。
4. thread\_executor语义：使用新`std::thread`执行延续。

前两种可能性有一个显著的缺点：它们会阻塞。第一种情况下，使用者阻塞，直到生产者准备好为止。第二种情况下，生产者阻塞，直到消费者准备好。

下面是文档[P0701r1](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0701r1.html)中的一些不错的executor传播用例:

```cpp
auto i = std::async(thread_pool, f).then(g).then(h);
// f, g and h are executed on thread_pool.

auto i = std::async(thread_pool, f).then(g, gpu).then(h);
// f is executed on thread_pool, g and h are executed on gpu.

auto i = std::async(inline_executor, f).then(g).then(h);
// h(g(f())) are invoked in the calling execution agent.
```

**将future传递给.then的延续是不明智的**

因为传递给continuation的是future，而不是它的值，所以语法非常复杂。越多的传递会让表达式变得非常复杂。

```cpp
std::future<int> f1 = std::async([]() { return 123; });
std::future<std::string> f2 = f1.then([](std::future<int> f) {
    return std::to_string(f.get());
});
```

现在，我假设这个值可以传递，因为`std::future<int>`重载了`to_string`。

使用`std::future`传递值的延续

```cpp
std::future<int> f1 = std::async([]() { return 123; });
std::future<std::string> f2 = f1.then(std::to_string);
```

**when\_all和when\_any的返回类型让人费解**

介绍`std::when_all`和`std::when_any`的这两章，展示了它们相当复杂的使用方法。

**future析构中的条件块必须去掉**

触发即忘的future看起来非常有用，但也有一个很大的限制。由`std::async`创建的future会等待它的析构函数，直到对应的promise完成。看起来并发的东西，实际是串行运行的。根据文档P0701r1的观点，这是不可接受的，并且非常容易出错。

我在参考章节中描述了触发即忘future的特殊行为。

**当前值和future值应该易于组合**

C++11中，没有简易的方法来创建future，必须从promise开始。

在当前标准中创造future

```cpp
std::promise<std::string> p;
std::future<std::string> fut = p.get_future();
p.set_value("hello");
```

这可能会因为并发技术规范v1中的`std::make_ready_future`函数而改变。

使用并发TS v1标准创建future

```cpp
std::future<std::string> fut = make_ready_future("hello");
```

使用future和非future参数将使我们的工作更加舒服。

```cpp
bool f(std::string, double, int);

std::future<std::string> a = /* ... */;
std::future<int> c = /* ... */;

std::future<bool> d1 = when_all(a, make_ready_future(3.14), c).then(f);
// f(a.get(), 3.14, c.get())

std::future<bool> d2 = when_all(a, 3.14, c).then(f);
// f(a.get(), 3.14, c.get())
```

并发技术标准v1中，`d1`和`d2`都是不可能的。

**五个新概念**

提案[1054R0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1054r0.html)提出了future和promise的5个新概念。

* FutureContinuation：使用future的值或异常作为参数调用的可调用对象。
* SemiFuture：它可以被绑定到一个执行器上，并产生一个`ContinuableFuture`的操作`(f = sf.via(exec))`。
* ContinuableFuture：它细化了SemiFuture，实例可以在`(f.then(c))`上附加一个`FutureContinuation`。当future处于ready状态时，就会在future关联执行器上执行。
* SharedFuture：它细化了ContinuableFuture，实例可以附加多个FutureContinuation。
* Promise：每一个promise都与一个future相关联，当future中设置好一个值或一个异常时，future处于ready状态。

文章还对这些新概念进行了详细描述。

future和promise的五个新概念

```cpp
template <typename T>
struct FutureContinuation
{
  // At least one of these two overloads exists:
  auto operator()(T value);
  auto operator()(exception_arg_t, exception_ptr exception);
};

template <typename T>
struct SemiFuture
{
  template <typename Executor>
  ContinuableFuture<Executor, T> via(Executor&& exec) &&;
};

template <typename Executor, typename T>
struct ContinuableFuture
{
  template <typename RExecutor>
  ContinuableFuture<RExecutor, T> via(RExecutor&& exec) &&;

  template <typename Continuation>
  ContinuableFuture<Executor, auto> then(Continuation&& c) &&;
};

template <typename Executor, typename T>
struct SharedFuture
{
  template <typename RExecutor>
  ContinuableFuture<RExecutor, auto> via(RExecutor&& exec);

  template <typename Continuation>
  SharedFuture<Executor, auto> then(Continuation&& c);
};

template <typename T>
struct Promise
{
  void set_value(T value) &&;

  template <typename Error>
  void set_exception(Error exception) &&;

  bool valid() const;
};
```

根据这些概念，提出一些意见:

* 可以使用值或异常调用FutureContinuation。它是一个可调用的单元，使用future的值或异常。
* 所有future(SemiFuture 、ContinuableFuture和SharedFuture)都有一个方法，可以通过该方法指定一个执行器并返回一个ContinuableFuture，并且可以通过使用不同的执行程序将一种future类型转换为另一种类型。
* 只有一个ContinuableFuture或SharedFuture有then方法用来继续。then方法可以接受FutureContinuation，并返回ContinuableFuture。
* SharedFuture是一个可复制的future 。
* Promise可以设置值或异常。

**未完成的工作**

[提案1054R0](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1054r0.html)中为未来留下了几个需要完成的工作：

* future和promise还有前进空间。
* 非并发执行代理使用future和promise时需要同步。
* `std::future/std::promise`的互操作性.
* future的展开，支持包括`future<future<T>>`的更高级形式。
* when\_all/when\_any/when\_n&#x20;
* async&#x20;


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://chenxiaowei.gitbook.io/concurrency-with-modern-c/xiang-xi-jie-shao/5.0-chinese/5.4-chinese.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
