Future
Future, 表示这个结果我会在未来进行消费. 那么它具体怎么生产出来玩无所谓. 我持有 std::future 只是一个消费者而已.
值得注意的是, 我们并不关注到底是通过协程, 线程, 还是什么神秘方式得到的这个结果, 我只关注在当前执行流中, 我未来会需要消费它而已 —— 即只关心数据本身
同步执行
int Calculate()
{
return 42;
}
int main()
{
int result = Calculate();
...
Use(result);
}在同步的执行流中, 在 Use(result) 之前, 必须等待 Calculate 的执行结束. 那么假设这个方法是一个耗时方法, 那么整个线程就会在那边等待Calculate返回结果, 比较坏. 而中间的 ... 只能干等.
future
所以我们使用 std::future 表示说我们希望可以有一个消费的数据, 当前线程继续执行, 在我们需要这个结果的时候再去取用.
所以它的优点就在于我们把发起和获得结果这两件事情给解偶了.
int Calculate()
{
std::this_thread::sleep_for(
std::chrono::seconds(2));
return 42;
}
int main()
{
std::future<int> future =
std::async(
std::launch::async,
Calculate);
std::cout << "Main continues\n";
int result = future.get();
std::cout << result << '\n';
}此处我们使用 async 来发起结果的获取, 然后调用 get() 进行数据的消费.
此时调用 get 可能有两个结果 ——
- 当前 result 已经创建好了, 不阻塞直接用
- 当前 result 还没有被创建好, 阻塞等待它好 (因为接下来操作依赖它, 所以不等不行)
不过, std::future 不会自己写好之后要进行消费的数据, 所以就需要一个生产者来加入当前的数据链条(埋钩子🪝ing)
非常值得小心的一点, std::future 是一次性消费 ——
auto future =
std::async(
std::launch::async,
[] {
return 114;
});
int a = future.get();
int b = future.get();经过一次消费之后, valid() 状态也会被设置成 False, 那么再次 .get() 可能触发 Undefined Behavior
与此同时, 还有一件事 —— future 对象本身是是“独占”所有权的, 也就是说, 无法复制, 只能移动.
std::future<int> a;
auto b = a; // ❌
auto b = std::move(a); // ✅好像可以理解成 —— 这个消费只能一次, 且消费权也仅能由一个对象独占
shared_future
那么对于有多个消费者想要对这个变量进行消费怎么办? —— std::shared_future
然后可以多次 .get() 了, 不过表示大家都只读一个共享的结果.
State
我们其实可以通过一个状态机的表现来进行理解 —— 可以看草案中的说明, 这个 shared state 包含状态信息, 以及一个可能尚未求值的结果; 结果可以是值或异常
请 GPT 再次展现 ASCII 艺术
Producer
│
│ set value / exception
▼
┌─────────────────┐
│ Shared State │
│ │
│ ready? │
│ value/exception │
└────────┬────────┘
│
│ read/wait
▼
Future所以实际上来说, 我们的 future 更像是一个 handle, 指向了那个 Shared State. 那么我们借助 unique_ptr/shared_ptr 可以类似同理来理解此处的 future/shared_future
其他
std::future<T> 也提供了 wait —— 阻塞等待, 但是不进行消费数值, 只有 get 才表示对数据进行消费
那么 wait_for 也是同样道理, 不再赘述.
不过值得注意 —— std::future 也会参与异常的传播
auto future =
std::async(
std::launch::async,
[]() -> int
{
throw std::runtime_error(
"failed");
});那么在消费端, 我们进行调用 ——
try
{
int result = future.get();
}
catch (const std::exception& e)
{
std::cout << e.what();
}此时就会捕获到 failed 这样的 std::runtime_error.
总结
在使用 future (或者相关的东西), 我们最应当考虑的是“我是否要在某时刻消费这个数据”, 而不是关心这个数据是线程/协程或其他什么之类的而得到.
即请关注数据本身, 以及操作/执行流本身!