Skip to content

Commit 1bfbf00

Browse files
committed
1. 修改阅读须知展示代码风格的示例
2. 修改 `std::future` 支持只能移动类型的示例 3. 新增一节“future 的状态变化”,主要聊 `get` 的特殊情况以及部分源码解析
1 parent a91adbd commit 1bfbf00

File tree

2 files changed

+72
-17
lines changed

2 files changed

+72
-17
lines changed

md/04同步操作.md

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -343,33 +343,36 @@ std::async(f, n); // OK! 可以通过编译,不过引用的并非是局部
343343
std::async(f2, n); // Error! 无法通过编译
344344
```
345345
346-
我们来展示使用 `std::move` ,也就移动传递参数
346+
我们来展示使用 `std::move` ,也就是移动传递参数并接受返回值
347347
348348
```cpp
349-
struct move_only {
349+
struct move_only{
350350
move_only() { std::puts("默认构造"); }
351-
move_only(const move_only&) = delete;
352-
move_only(move_only&&)noexcept {
353-
std::puts("移动构造");
351+
move_only(move_only&&)noexcept { std::puts("移动构造"); }
352+
move_only& operator=(move_only&&) noexcept {
353+
std::puts("移动赋值");
354+
return *this;
354355
}
356+
move_only(const move_only&) = delete;
355357
};
356358
357-
void task(move_only x){
359+
move_only task(move_only x){
358360
std::cout << "异步任务 ID: " << std::this_thread::get_id() << '\n';
361+
return x;
359362
}
360363
361364
int main(){
362365
move_only x;
363-
std::future<void> future = std::async(task, std::move(x));
364-
std::this_thread::sleep_for(std::chrono::milliseconds(1));
366+
std::future<move_only> future = std::async(task, std::move(x));
367+
std::this_thread::sleep_for(std::chrono::seconds(1));
365368
std::cout << "main\n";
366-
future.wait(); // 等待异步任务执行完毕
369+
move_only result = future.get(); // 等待异步任务执行完毕
367370
}
368371
```
369372

370-
> [运行](https://godbolt.org/z/fY9Md3nzz)测试。
373+
> [运行](https://godbolt.org/z/5cfvdEWWh)测试。
371374
372-
如你所见,它**支持只移动类型**,我们将参数使用 `std::move` 传递。
375+
如你所见,它**支持只移动类型**,我们将参数使用 `std::move` 传递,接收参数的时候直接调用 `get` 函数即可
373376

374377
---
375378

@@ -672,6 +675,57 @@ int main() {
672675
来自 set_exception 的异常: promise already satisfied
673676
```
674677

678+
### future 的状态变化
679+
680+
需要注意的是,**future 是一次性的**,所以你需要注意移动。并且,调用 `get` 函数后,future 对象也会**失去共享状态**
681+
682+
- **移动语义**:这一点很好理解并且常见,因为**移动操作标志着所有权的转移**,意味着 `future` 不再拥有共享状态(如之前所提到)。`get``wait` 函数要求 `future` 对象拥有共享状态,否则会抛出异常。
683+
- **共享状态失效**:调用 `get` 成员函数时,`future` 对象必须拥有共享状态,但调用完成后,它就会**失去共享状态**,不能再次调用 `get`。这是我们在本节需要特别讨论的内容。
684+
685+
```cpp
686+
std::future<void>future = std::async([] {});
687+
std::cout << std::boolalpha << future.valid() << '\n'; // true
688+
future.get();
689+
std::cout << std::boolalpha << future.valid() << '\n'; // false
690+
try {
691+
future.get(); // 抛出 future_errc::no_state 异常
692+
}
693+
catch (std::exception& e) {
694+
std::cerr << e.what() << '\n';
695+
}
696+
```
697+
698+
> [运行](https://godbolt.org/z/hvfMGnMbj)测试。
699+
700+
这个问题在许多文档中没有明确说明,但通过阅读源码([MSVC STL](https://github.com/microsoft/STL/blob/f54203f/stl/inc/future)),可以很清楚地理解:
701+
702+
```cpp
703+
// std::future<void>
704+
void get() {
705+
// block until ready then return or throw the stored exception
706+
future _Local{_STD move(*this)};
707+
_Local._Get_value();
708+
}
709+
// std::future<T>
710+
_Ty get() {
711+
// block until ready then return the stored result or throw the stored exception
712+
future _Local{_STD move(*this)};
713+
return _STD move(_Local._Get_value());
714+
}
715+
// std::future<T&>
716+
_Ty& get() {
717+
// block until ready then return the stored result or throw the stored exception
718+
future _Local{_STD move(*this)};
719+
return *_Local._Get_value();
720+
}
721+
```
722+
723+
如上所示,我们展示了 `std::future` 的所有特化 `get` 成员函数的实现。注意到了吗?尽管我们可能不了解移动构造函数的具体实现,但根据通用的语义,可以看出 `future _Local{_STD move(*this)};` 将当前对象的共享状态转移给了这个局部对象,而局部对象在函数结束时析构。这意味着当前对象失去共享状态,并且状态被完全销毁。
724+
725+
另外一提,`std::future<T>` 这个特化,它 `return std::move` 是为了支持只能移动的类型能够使用 `get` 返回值,参见前文的 `move_only` 类型。
726+
727+
如果需要进行多次 `get` 调用,可以考虑使用下文提到的 `std::shared_future`
728+
675729
### 多个线程的等待
676730

677731
之前的例子中都在用 `std::future` ,不过 `std::future` 也有局限性。很多线程在等待的时候,只有一个线程能获取结果。当多个线程等待相同事件的结果时,就需要使用 `std::shared_future` 来替代 `std::future` 了。`std::future``std::shared_future` 的区别就如同 `std::unique_ptr``std::shared_ptr` 一样。

md/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,17 @@
2121
&emsp;&emsp;我们的代码风格较为简洁明了,命名全部使用下划线连接,而不是驼峰命名法。花括号通常只占一行,简短的代码可以不额外占行。一般初始化时使用 `{}`,而非 `()` 或者 `=` 。这样简单直观,避免歧义和许多问题。`#include` 引入头文件时需要在尖括号或引号前后加空格。
2222

2323
```cpp
24-
#include <iostream> // 空格
25-
struct move_only {
24+
struct move_only{
2625
move_only() { std::puts("默认构造"); }
27-
move_only(const move_only&) = delete;
28-
move_only(move_only&&)noexcept {
29-
std::puts("移动构造");
26+
move_only(move_only&&)noexcept { std::puts("移动构造"); }
27+
move_only& operator=(move_only&&) noexcept {
28+
std::puts("移动赋值");
29+
return *this;
3030
}
31+
move_only(const move_only&) = delete;
3132
};
3233

33-
int main() {
34+
int main(){
3435
move_only m{};
3536
char buffer[1024]{} // 全部初始化为 0
3637
}

0 commit comments

Comments
 (0)