Update item37.md

This commit is contained in:
猫耳堀川雷鼓 2021-03-15 20:59:34 +08:00 committed by GitHub
parent 8909979986
commit 91d728cd0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -8,8 +8,8 @@
- **默认构造的`std::thread`s**。这种`std::thread`没有函数执行,因此没有对应到底层执行线程上。
- **已经被移动走的`std::thread`对象**。移动的结果就是一个`std::thread`原来对应的执行线程现在对应于另一个`std::thread`。
- **已经被`join`的`std::thread`**。在`join`之后,`std::thread`不再对应于已经运行完了的执行线程。
- **已经被`detach`的`std::thread`**。`detach`断开了`std::thread`对象与执行线程之间的连接。
- **已经被`join`的`std::thread`** 。在`join`之后,`std::thread`不再对应于已经运行完了的执行线程。
- **已经被`detach`的`std::thread`** 。`detach`断开了`std::thread`对象与执行线程之间的连接。
(译者注:`std::thread`可以视作状态保存的对象,保存的状态可能也包括可调用对象,有没有具体的线程承载就是有没有连接)
@ -59,9 +59,9 @@ constexpr auto tenMillion = 10'000'000; //C++14
你可能会想,为什么`std::thread`析构的行为是这样的,那是因为另外两种显而易见的方式更糟:
- **隐式`join`**。这种情况下,`std::thread`的析构函数将等待其底层的异步执行线程完成。这听起来是合理的,但是可能会导致难以追踪的异常表现。比如,如果`conditonAreStatisfied()`已经返回了`false``doWork`继续等待过滤器应用于所有值就很违反直觉。
- **隐式`join`** 。这种情况下,`std::thread`的析构函数将等待其底层的异步执行线程完成。这听起来是合理的,但是可能会导致难以追踪的异常表现。比如,如果`conditonAreStatisfied()`已经返回了`false``doWork`继续等待过滤器应用于所有值就很违反直觉。
- **隐式`detach`**。这种情况下,`std::thread`析构函数会分离`std::thread`与其底层的线程。底层线程继续运行。听起来比`join`的方式好,但是可能导致更严重的调试问题。比如,在`doWork`中,`goodVals`是通过引用捕获的局部变量。它也被*lambda*修改(通过调用`push_back`)。假定,*lambda*异步执行时,`conditionsAreSatisfied()`返回`false`。这时,`doWork`返回,同时局部变量(包括`goodVals`)被销毁。栈被弹出,并在`doWork`的调用点继续执行线程。
- **隐式`detach`** 。这种情况下,`std::thread`析构函数会分离`std::thread`与其底层的线程。底层线程继续运行。听起来比`join`的方式好,但是可能导致更严重的调试问题。比如,在`doWork`中,`goodVals`是通过引用捕获的局部变量。它也被*lambda*修改(通过调用`push_back`)。假定,*lambda*异步执行时,`conditionsAreSatisfied()`返回`false`。这时,`doWork`返回,同时局部变量(包括`goodVals`)被销毁。栈被弹出,并在`doWork`的调用点继续执行线程。
调用点之后的语句有时会进行其他函数调用,并且至少一个这样的调用可能会占用曾经被`doWork`使用的栈位置。我们调用那么一个函数`f`。当`f`运行时,`doWork`启动的*lambda*仍在继续异步运行。该*lambda*可能在栈内存上调用`push_back`,该内存曾属于`goodVals`,但是现在是`f`的栈内存的某个位置。这意味着对`f`来说,内存被自动修改了!想象一下调试的时候“乐趣”吧。
@ -104,7 +104,7 @@ private:
- 构造器只接受`std::thread`右值,因为我们想要把传来的`std::thread`对象移动进`ThreadRAII`。(`std::thread`不可以复制。)
- 构造器的参顺序设计的符合调用者直觉(首先传递`std::thread`,然后选择析构执行的动作,这比反过来更合理),但是成员初始化列表设计的匹配成员声明的顺序。将`std::thread`对象放在声明最后。在这个类中,这个顺序没什么特别之处,但是通常,可能一个数据成员的初始化依赖于另一个,因为`std::thread`对象可能会在初始化结束后就立即执行函数了,所以在最后声明是一个好习惯。这样就能保证一旦构造结束,在前面的所有数据成员都初始化完毕,可以供`std::thread`数据成员绑定的异步运行的线程安全使用。
- 构造器的参顺序设计的符合调用者直觉(首先传递`std::thread`,然后选择析构执行的动作,这比反过来更合理),但是成员初始化列表设计的匹配成员声明的顺序。将`std::thread`对象放在声明最后。在这个类中,这个顺序没什么特别之处,但是通常,可能一个数据成员的初始化依赖于另一个,因为`std::thread`对象可能会在初始化结束后就立即执行函数了,所以在最后声明是一个好习惯。这样就能保证一旦构造结束,在前面的所有数据成员都初始化完毕,可以供`std::thread`数据成员绑定的异步运行的线程安全使用。
- `ThreadRAII`提供了`get`函数访问内部的`std::thread`对象。这类似于标准智能指针提供的`get`函数,可以提供访问原始指针的入口。提供`get`函数避免了`ThreadRAII`复制完整`std::thread`接口的需要,也意味着`ThreadRAII`可以在需要`std::thread`对象的上下文环境中使用。