Skip to content

Commit 1a1e8eb

Browse files
committed
更新 async 与 future 源码解析的内容 #12
1 parent 566e3b2 commit 1a1e8eb

File tree

1 file changed

+100
-5
lines changed

1 file changed

+100
-5
lines changed

Diff for: md/详细分析/03async与future源码解析.md

+100-5
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44

55
和之前一样的,我们以 MSVC STL 的实现进行讲解。
66

7-
`std::future`,即未来体,是用来管理一个共享状态的类模板,用于等待关联任务的完成并获取其返回值。它自身不包含状态,需要通过如 `std::async` 之类的函数进行初始化。`std::async` 函数模板返回一个已经初始化且具有共享状态的 `std::future` 对象。因此,所有操作的开始应从 `std::async` 开始讲述。
7+
`std::future`,即未来体,是用来管理一个共享状态的类模板,用于等待关联任务的完成并获取其返回值。它自身不包含状态,需要通过如 `std::async` 之类的函数进行初始化。`std::async` 函数模板返回一个已经初始化且具有共享状态的 `std::future` 对象。
8+
9+
因此,所有操作的开始应从 `std::async` 开始讲述。
10+
11+
需要注意的是,它们的实现彼此之间会共用不少设施,在讲述 `std::async` 源码的时候,对于 `std::future` 的内容同样重要。
812

913
> MSVC STL 很早之前就不支持 C++11 了,它的实现完全基于 **C++14**,出于某些原因 **C++17** 的一些库(如 [`invoke`](https://zh.cppreference.com/w/cpp/utility/functional/invoke)_v 变量模板)被向后移植到了 **C++14** 模式,所以即使是 C++11 标准库设施,实现中可能也是使用到了 C++14、17 的东西。
1014
>
1115
> 注意,不用感到奇怪。
1216
13-
## 正式
17+
## `std::async`
1418

1519
```cpp
1620
_EXPORT_STD template <class _Fty, class... _ArgTypes>
@@ -107,7 +111,7 @@ _NODISCARD_ASYNC future<_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>>
107111

108112
5. `_Promise<_Ptype> _Pr`
109113

110-
`_Promise` 类型本身不重要,很简单,关键还在于其存储的数据成员。
114+
[`_Promise`](https://github.com/microsoft/STL/blob/f54203f/stl/inc/future#L999-L1055) 类型本身不重要,很简单,关键还在于其存储的数据成员。
111115

112116
```cpp
113117
template <class _Ty>
@@ -169,10 +173,101 @@ _NODISCARD_ASYNC future<_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>>
169173
};
170174
```
171175
172-
`_Promise` 类是对 `_State_manager` 类型的**包装**,并增加了一个表示状态的成员 `_Future_retrieved`。
176+
`_Promise` 类模板是对 **[`_State_manager`](https://github.com/microsoft/STL/blob/f54203f/stl/inc/future#L688-L825) 类模板的包装**,并增加了一个表示状态的成员 `_Future_retrieved`。
173177
174178
状态成员用于跟踪 `_Promise` 是否已经调用过 `_Get_state_for_future()` 成员函数;它默认为 `false`,在**第一次**调用 `_Get_state_for_future()` 成员函数时被置为 `true`,如果二次调用,就会抛出 [`future_errc::future_already_retrieved`](https://zh.cppreference.com/w/cpp/thread/future_errc) 异常。
175179
176180
> 这类似于 `std::promise` 调用 [`get_future()`](https://zh.cppreference.com/w/cpp/thread/promise/get_future) 成员函数。[测试](https://godbolt.org/z/8anc9b3PT)。
181+
182+
`_Promise` 的构造函数接受的却不是 `_State_manager` 类型的对象,而是 `_Associated_state` 类型的指针,用来初始化数据成员 `_State`。
183+
184+
```cpp
185+
_State(_State_ptr, false)
186+
```
187+
188+
这是因为实际上 `_State_manager` 类型的[实现](https://github.com/microsoft/STL/blob/f54203f/stl/inc/future#L822-L824)就是保有了 [`Associated_state`](https://github.com/microsoft/STL/blob/f54203f/stl/inc/future#L198-L445) 指针,以及一个状态成员:
189+
190+
```cpp
191+
private:
192+
_Associated_state<_Ty>* _Assoc_state = nullptr;
193+
bool _Get_only_once = false;
194+
```
195+
196+
也可以简单理解 `_State_manager` 又是对 `Associated_state` 的包装,其中的大部分接口实际上是调用 `_Assoc_state` 的成员函数,如:
197+
198+
```cpp
199+
void wait() const { // wait for signal
200+
if (!valid()) {
201+
_Throw_future_error2(future_errc::no_state);
202+
}
203+
204+
_Assoc_state->_Wait();
205+
}
206+
```
207+
208+
- **一切的重点,最终在 `Associated_state` 上**。
209+
210+
然而它也是最为复杂的,我们在讲 `std::thread`-构造源码解析 中提到过一句话:
211+
212+
> - **了解一个庞大的类,最简单的方式就是先看它的数据成员有什么**。
213+
214+
```cpp
215+
public:
216+
conditional_t<is_default_constructible_v<_Ty>, _Ty, _Result_holder<_Ty>> _Result;
217+
exception_ptr _Exception;
218+
mutex _Mtx;
219+
condition_variable _Cond;
220+
bool _Retrieved;
221+
int _Ready;
222+
bool _Ready_at_thread_exit; // TRANSITION, ABI
223+
bool _Has_stored_result;
224+
bool _Running;
225+
```
226+
227+
这是 `Associated_state` 的数据成员,其中有许多的 `bool` 类型的状态成员,同时最为明显重要的三个设施是:**异常指针**、**互斥量**、**条件变量**。
228+
229+
根据这些数据成员我们就能很轻松的猜测出 `Associated_state` 模板类的作用和工作方式。
230+
231+
- **异常指针**:用于存储异步任务中可能发生的异常,以便在调用 `future::get` 时能够重新抛出异常。
232+
- **互斥量和条件变量**:用于在异步任务和等待任务之间进行同步。当异步任务完成时,条件变量会通知等待的任务。
233+
234+
`_Associated_state` 模板类负责管理异步任务的状态,包括结果的存储、异常的处理以及任务完成的通知。它是实现 `std::future``std::promise` 的核心组件之一,通过 `_State_manager``_Promise` 类模板对其进行封装和管理,提供更高级别的接口和功能。
235+
236+
```plaintext
237+
+---------------------+
238+
| _Promise<_Ty> |
239+
|---------------------|
240+
| - _State | -----> +---------------------+
241+
| - _Future_retrieved | | _State_manager<_Ty> |
242+
+---------------------+ |----------------------|
243+
| - _Assoc_state | -----> +-------------------------+
244+
| - _Get_only_once | | _Associated_state<_Ty> |
245+
+----------------------+ +-------------------------+
177246
178-
`_Promise` 的构造函数
247+
```
248+
249+
上图是 `_Promise`_`State_manager``_Associated_state` 之间的**包含关系示意图**,理解这个关系对我们后面**非常重要**
250+
251+
到此就可以了,我们不需要在此处就详细介绍这三个类,但是你需要大概的看一下,这非常重要。
252+
253+
6. 初始化 `_Promie` 对象:
254+
255+
`_Get_associated_state<_Ret>(_Policy, _Fake_no_copy_callable_adapter<_Fty, _ArgTypes...>(_STD forward<_Fty>(_Fnarg), _STD forward<_ArgTypes>(_Args)...))`
256+
257+
很明显,这是一个函数调用,将我们 `std::async` 的参数全部转发给它,它是重要而直观的,代码如下:
258+
259+
```cpp
260+
template <class _Ret, class _Fty>
261+
_Associated_state<typename _P_arg_type<_Ret>::type>* _Get_associated_state(launch _Psync, _Fty&& _Fnarg) {
262+
// construct associated asynchronous state object for the launch type
263+
switch (_Psync) { // select launch type
264+
case launch::deferred:
265+
return new _Deferred_async_state<_Ret>(_STD forward<_Fty>(_Fnarg));
266+
case launch::async: // TRANSITION, fixed in vMajorNext, should create a new thread here
267+
default:
268+
return new _Task_async_state<_Ret>(_STD forward<_Fty>(_Fnarg));
269+
}
270+
}
271+
```
272+
273+
在 MSVC STL 的实现中,`launch::async | launch::deferred` 与 `launch::async` 执行策略**毫无区别**。这段代码的 `switch` 语句有两个 `case` 和一个 `default`,如你所见,`case launch::async` 是为空的,除了 `launch::deferred` 最终都会进入 `default`,即都执行 `launch::async` 策略。

0 commit comments

Comments
 (0)