|
4 | 4 |
|
5 | 5 | 和之前一样的,我们以 MSVC STL 的实现进行讲解。
|
6 | 6 |
|
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` 的内容同样重要。 |
8 | 12 |
|
9 | 13 | > 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 的东西。
|
10 | 14 | >
|
11 | 15 | > 注意,不用感到奇怪。
|
12 | 16 |
|
13 |
| -## 正式 |
| 17 | +## `std::async` |
14 | 18 |
|
15 | 19 | ```cpp
|
16 | 20 | _EXPORT_STD template <class _Fty, class... _ArgTypes>
|
@@ -107,7 +111,7 @@ _NODISCARD_ASYNC future<_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>>
|
107 | 111 |
|
108 | 112 | 5. `_Promise<_Ptype> _Pr`
|
109 | 113 |
|
110 |
| - `_Promise` 类型本身不重要,很简单,关键还在于其存储的数据成员。 |
| 114 | + [`_Promise`](https://github.com/microsoft/STL/blob/f54203f/stl/inc/future#L999-L1055) 类型本身不重要,很简单,关键还在于其存储的数据成员。 |
111 | 115 |
|
112 | 116 | ```cpp
|
113 | 117 | template <class _Ty>
|
@@ -169,10 +173,101 @@ _NODISCARD_ASYNC future<_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>>
|
169 | 173 | };
|
170 | 174 | ```
|
171 | 175 |
|
172 |
| - `_Promise` 类是对 `_State_manager` 类型的**包装**,并增加了一个表示状态的成员 `_Future_retrieved`。 |
| 176 | + `_Promise` 类模板是对 **[`_State_manager`](https://github.com/microsoft/STL/blob/f54203f/stl/inc/future#L688-L825) 类模板的包装**,并增加了一个表示状态的成员 `_Future_retrieved`。 |
173 | 177 |
|
174 | 178 | 状态成员用于跟踪 `_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) 异常。
|
175 | 179 |
|
176 | 180 | > 这类似于 `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 | + +----------------------+ +-------------------------+ |
177 | 246 |
|
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