mirror of
https://github.com/CnTransGroup/EffectiveModernCppChinese.git
synced 2025-01-01 07:50:15 +08:00
add 7. concurrency
This commit is contained in:
parent
0788bd3685
commit
87c58e9286
35
7.The Concurrency API/Item35.md
Normal file
35
7.The Concurrency API/Item35.md
Normal file
@ -0,0 +1,35 @@
|
||||
# 优先基于任务编程而不是基于线程
|
||||
如果开发者想要异步执行 `doAsyncWork` 函数,通常有两种方式。其一是通过创建 `std::thread` 执行 `doAsyncWork`, 比如
|
||||
```cpp
|
||||
int doAsyncWork();
|
||||
std::thread t(doAsyncWork);
|
||||
```
|
||||
其二是将 `doAsyncWork` 传递给 `std::async`, 一种基于任务的策略:
|
||||
```cpp
|
||||
auto fut = std::async(doAsyncWork); // "fut" for "future"
|
||||
```
|
||||
这种方式中,函数对象作为一个任务传递给 `std::async`。
|
||||
|
||||
基于任务的方法通常比基于线程的方法更优,原因之一上面的代码已经表明,基于任务的方法代码量更少。我们假设唤醒`doAsyncWork`的代码对于其提供的返回值是有需求的。基于线程的方法对此无能为力,而基于任务的方法可以简单地获取`std::async`返回的`future`提供的`get`函数获取这个返回值。如果`doAsycnWork`发生了异常,`get`函数就显得更为重要,因为`get`函数可以提供抛出异常的访问,而基于线程的方法,如果`doAsyncWork`抛出了异常,线程会直接终止(通过调用`std::terminate`)。
|
||||
|
||||
基于线程与基于任务最根本的区别在于抽象层次的高低。基于任务的方式使得开发者从线程管理的细节中解放出来,对此在C++并发软件中总结了'thread'的三种含义:
|
||||
|
||||
- 硬件线程(Hardware threads)是真实执行计算的线程。现代计算机体系结构为每个CPU核心提供一个或者多个硬件线程。
|
||||
- 软件线程(Software threads)(也被称为系统线程)是操作系统管理的在硬件线程上执行的线程。通常可以存在比硬件线程更多数量的软件线程,因为当软件线程被比如 I/O、同步锁或者条件变量阻塞的时候,操作系统可以调度其他未阻塞的软件线程执行提供吞吐量。
|
||||
- `std::threads`是C++执行过程的对象,并作为软件线程的handle(句柄)。`std::threads`存在多种状态,1. `null`表示空句柄,因为处于默认构造状态(即没有函数来执行),因此不对应任何软件线程。 2. moved from (moved-to的`std::thread`就对应软件进程开始执行) 3. `joined`(连接唤醒与被唤醒的两个线程) 4. `detached`(将两个连接的线程分离)
|
||||
|
||||
软件线程是有限的资源。如果开发者试图创建大于系统支持的硬件线程数量,会抛出`std::system_error`异常。即使你编写了不抛出异常的代码,这仍然会发生,比如下面的代码,即使 `doAsyncWork`是 `noexcept`
|
||||
```cpp
|
||||
int doAsyncWork() noexcept; // see Item 14 for noexcept
|
||||
```
|
||||
这段代码仍然会抛出异常。
|
||||
```cpp
|
||||
std::thread t(doAsyncWork); // throw if no more
|
||||
// threads are available
|
||||
```
|
||||
|
||||
设计良好的软件必须有效地处理这种可能性(软件线程资源耗尽),一种有效的方法是在当前线程执行`doAsyncWork`,但是这可能会导致负载不均,而且如果当前线程是GUI线程,可能会导致响应时间过长的问题;另一种方法是等待当前运行的线程结束之后再创建新的线程,但是仍然有可能当前运行的线程在等待`doAsyncWork`的结果(例如操作得到的变量或者条件变量的通知)。
|
||||
|
||||
即使没有超出软件线程的限额,仍然可能会遇到资源超额的麻烦。如果当前准备运行的软件线程大于硬件线程的数量,系统的线程调度程序会将硬件核心的时间切片,当一个软件线程的时间片执行结束,会让给另一个软件线程,即发生上下文切换。软件线程的上下文切换会增加系统的软件线程管理开销,并且如果发生了硬件核心漂移,这个开销会更高,具体来说,如果发生了硬件核心漂移,(1)CPU cache中关于上次执行线程的数据很少,需要重新加载指令;(2)新线程的cache数据会覆盖老线程的数据,如果将来会再次覆盖老线程的数据,显然频繁覆盖增加很多切换开销。
|
||||
|
||||
避免资源超额是困难的,因为软件线程之于硬件线程的最佳比例取决于软件线程的执行频率,
|
Loading…
Reference in New Issue
Block a user