Skip to content

Commit f61274a

Browse files
committed
为使用线程中“当前环境支持并发线程数”一章中添加额外示例与讲解
1 parent 0d281ef commit f61274a

File tree

1 file changed

+72
-0
lines changed

1 file changed

+72
-0
lines changed

md/02使用线程.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,78 @@ int main(){
7272

7373
- **在进行多线程编程的时候,我们可以参考此值确定我们要创建的线程数量**
7474

75+
---
76+
77+
我们可以举个简单的例子运用这个值:
78+
79+
```cpp
80+
template<typename InputIt>
81+
auto sum(InputIt first, InputIt last){
82+
using value_type = std::remove_cvref_t<decltype(*first)>;
83+
std::size_t num_threads = std::thread::hardware_concurrency();
84+
std::ptrdiff_t distance = std::distance(first, last);
85+
86+
if(distance > 1024000){
87+
// 计算每个线程处理的元素数量
88+
std::size_t chunk_size = distance / num_threads;
89+
std::size_t remainder = distance % num_threads;
90+
91+
// 存储每个线程的结果
92+
std::vector<value_type>results(num_threads);
93+
94+
// 存储关联线程的线程对象
95+
std::vector<std::thread> threads;
96+
97+
// 创建并启动线程
98+
InputIt start = first;
99+
for (std::size_t i = 0; i < num_threads; ++i) {
100+
InputIt end = std::next(start, chunk_size + (i < remainder ? 1 : 0));
101+
threads.emplace_back([start, end, &results, i] {
102+
results[i] = std::accumulate(start, end, value_type{});
103+
});
104+
start = end; // 开始迭代器不断向前
105+
}
106+
107+
// 等待所有线程执行完毕
108+
for (auto& thread : threads)
109+
thread.join();
110+
111+
// 汇总线程的计算结果
112+
value_type total_sum = std::accumulate(results.begin(), results.end(), value_type{});
113+
return total_sum;
114+
}
115+
116+
value_type total_sum = std::accumulate(first, last, value_type{});
117+
return total_sum;
118+
}
119+
```
120+
121+
> [运行](https://godbolt.org/z/YE7q1qqcK)测试。
122+
123+
我们写了这样一个求和函数 `sum`,接受两个迭代器计算它们范围中对象的和。
124+
125+
我们先获取了迭代器所指向的值的类型,定义了一个别名 `value_type`,记得删除 CV 与引用,我们这里使用到的 [`std::remove_cvref`](https://zh.cppreference.com/w/cpp/types/remove_cvref) 是 C++20 引入的。如果希望代码可以在 C++11 的环境运行也可以自行修改。`num_threads` 是当前硬件支持的并发线程的值。[`std::distance`](https://zh.cppreference.com/w/cpp/iterator/distance) 用来计算 first 到 last 的距离,也就是我们要进行求和的元素个数了。
126+
127+
我们这里的设计比较简单,毕竟是初学,所以只对元素个数大于 **`1024000`** 的进行多线程求和,而小于这个值的则直接使用标准库函数 [`std::accumulate`](https://zh.cppreference.com/w/cpp/algorithm/accumulate) 求和即可。
128+
129+
多线程求和只需要介绍**三个**地方
130+
131+
1. `chunk_size` 是每个线程分配的任务,但是这是可能有余数的,比如 10 个任务分配三个线程,必然余 1。但是我们也需要执行这个任务,所以还定义了一个对象 `remainder` ,它存储的就是余数。
132+
133+
2. `InputIt end = std::next(start, chunk_size + (i < remainder ? 1 : 0));` 这行代码是获取当前线程的执行范围,其实也就是要 `chunk_size` 再加上我们的余数 `remainder` 。这里写了一个三目运算符是为了进行分配任务,比如:
134+
135+
假设有 3 个线程执行,并且余数是 2。那么,每个线程的处理情况如下:
136+
137+
- 当 `i = 0` 时,由于 `0 < 2`,所以这个线程会多分配一个元素。
138+
- 当 `i = 1` 时,同样因为 `1 < 2`,这个线程也会多分配一个元素。
139+
- 当 `i = 2` 时,由于 `2 >= 2`,所以这个线程只处理平均数量的元素。
140+
141+
这确保了**剩余**的 2 个元素被分配给了前两个线程,而第三个线程只处理了平均数量的元素。这样就确保了所有的元素都被正确地分配给了各个线程进行处理。
142+
143+
3. `InputIt start = first;` 在创建线程执行之前先定义了一个开始迭代器。在传递给线程执行的lambda表达式中,最后一行是:`start = end;` 这是为了让迭代器一直向前。
144+
145+
由于求和不涉及数据竞争之类的问题,所以我们甚至可以在刚讲完 `Hello World` 就手搓了一个“**并行求和**”的简单的模板函数。主要的难度其实在于对 C++ 的熟悉程度,而非对线程类 `std::thread` 的使用了,这里反而是最简单的,无非是用容器存储线程对象管理,最后进行 `join()` 罢了。
146+
75147
## 线程管理
76148
77149
在 C++ 标准库中,只能管理与 `std::thread` 关联的线程,类 `std::thread` 的对象就是指代线程的对象,我们说“线程管理”,其实也就是管理 `std::thread` 对象。

0 commit comments

Comments
 (0)