
C++17引入的执行策略,说白了,就是给标准库算法加了个“加速开关”,让我们能更方便地利用多核CPU的算力,把一些原本串行执行的操作变成并行。它提供了一种声明式的写法,你告诉编译器和运行时库,某个算法可以怎么跑,是顺序跑,还是可以并行跑,甚至可以乱序跑,而不用我们自己去操心线程池、任务调度这些复杂的底层细节。
C++17在
std::execution
seq
par
par_unseq
std::execution::seq
std::execution::par
std::for_each
par
std::execution::par_unseq
怎么用呢? 简单得很,你只需要把执行策略作为标准库算法的第一个参数传进去就行。
#include <vector>
#include <algorithm>
#include <execution> // 包含执行策略
int main() {
std::vector<int> data(1000000);
// 填充数据...
// 顺序排序
std::sort(data.begin(), data.end());
// 并行排序
std::sort(std::execution::par, data.begin(), data.end());
// 并行且乱序转换(例如,每个元素加1)
std::transform(std::execution::par_unseq,
data.begin(), data.end(), data.begin(),
[](int x){ return x + 1; });
// 还有一些算法,比如std::reduce, std::inclusive_scan, std::exclusive_scan等
// 都可以配合执行策略使用。
return 0;
}我个人觉得,C++17执行策略的真正价值在于它把并行编程的门槛大大降低了。你不再需要自己去写线程池,也不用担心线程的创建销毁开销。库的实现者会根据你的硬件和具体算法,选择最优的并行化方案。但这不代表你可以高枕无忧,一些常见的并行化陷阱,比如数据竞争,依然需要你手动处理。而且,并不是所有算法都支持所有策略,也不是并行化了就一定更快,这些都是我们需要仔细考量的。
立即学习“C++免费学习笔记(深入)”;
这是一个很现实的问题。很多时候,我们一听到“并行”就觉得性能会爆炸式提升,但现实往往会给你泼一盆冷水。C++17的并行策略,它确实有潜力让你的代码“飞”起来,但它绝不是一颗万能的银弹。性能提升与否,甚至是不是负提升,取决于好几个关键因素。
首先,计算密集度是核心。如果你的算法大部分时间都在做复杂的数学运算、图像处理或者大规模数据分析,那并行化带来的收益会非常明显。因为这些操作是CPU密集型的,多核能同时跑,自然效率就高。但如果你的代码大部分时间都在等待I/O(比如读写文件、网络通信),或者被大量的锁竞争拖慢,那并行化效果可能微乎其微,甚至因为线程调度和同步的开销,比串行还慢。
其次,数据规模也很重要。对于小规模的数据集,并行化的调度开销(比如创建线程、分配任务、合并结果)可能会远远超过并行计算节省下来的时间。举个例子,对一个只有100个元素的数组进行并行排序,可能还不如直接串行排序来得快。通常,数据量达到几十万、上百万级别,并行化的优势才能真正体现出来。
再者,硬件架构。你有多少个CPU核心?你的CPU是否支持SIMD指令集(比如AVX-512)?缓存结构怎么样?这些都会直接影响并行策略的实际效果。
par_unseq
par
seq
最后,算法本身的可并行性。有些算法天生就容易并行化,比如对数组的每个元素独立操作(
for_each
transform
我的经验是,永远不要凭感觉判断并行化效果。你觉得它会快,它不一定快。你觉得它不会快,它可能反而有惊喜。测量,测量,再测量! 使用专业的性能分析工具(比如Linux下的
perf
std::accumulate
面对
seq
par
par_unseq
std::execution::seq
当你需要绝对的执行顺序保证时,或者你的算法对顺序有严格依赖时,
seq
seq
seq
par
par_unseq
seq
std::execution::par
这是我最常使用的并行策略。当你发现某个算法的计算量很大,而且各个元素的处理相对独立,结果不依赖于处理顺序时,
par
std::for_each
std::transform
std::sort
std::reduce
std::execution::par_unseq
这是最激进的策略,它允许并行,也允许“乱序”执行。这里的“乱序”不光是指多个线程并行,还指编译器和运行时库可以对你的代码进行向量化(SIMD)优化,在单个核心上同时处理多个数据。这意味着,如果你的操作是纯粹的、没有副作用的、且可以向量化的(比如简单的数值运算),
par_unseq
par
但是,它对代码的要求也更高。如果你的操作有任何副作用,或者依赖于严格的顺序(比如一个操作的结果作为下一个操作的输入),那么
par_unseq
transform
par_unseq
我的决策小流程:
seq
par
par_unseq
par
记住,永远要测试!在实际应用中,性能瓶颈可能出现在你意想不到的地方。
这是个老生常谈,但又极其容易踩坑的问题。C++17的执行策略确实让并行编程变得简单,但它只解决了“如何并行调度”的问题,并没有自动帮你解决“并行执行时的数据安全”问题。换句话说,执行策略不会神奇地让你的非线程安全代码变得线程安全。如果你在并行执行的任务中访问或修改了共享数据,而没有进行适当的同步,那么恭喜你,你已经成功地制造了一个数据竞争(Data Race)。
什么是数据竞争?
简单来说,就是两个或多个线程同时访问同一个内存位置,并且至少有一个是写操作,而且它们之间没有进行任何同步。数据竞争会导致未定义行为(Undefined Behavior),这意味着你的程序可能会崩溃,可能会输出错误结果,也可能在你的测试环境里表现正常,但在用户机器上突然爆炸。这种错误是最难调试的,因为它往往是间歇性的,难以复现。
常见的陷阱:
捕获外部变量的Lambda表达式: 这是最常见的陷阱之一。如果你在
std::for_each
std::transform
[&]
#include <vector>
#include <numeric>
#include <execution>
#include <iostream>
#include <mutex> // for mutex example
#include <atomic> // for atomic example
int main() {
std::vector<int> data(1000000, 1);
long long sum_bad = 0; // 共享变量
// 错误示范:数据竞争!sum_bad会被多个线程同时修改
// std::for_each(std::execution::par, data.begin(), data.end(), [&](int x) {
// sum_bad += x;
// });
// std::cout << "Bad Sum (likely incorrect): " << sum_bad << std::endl;
// 这段代码很可能不会得到1000000,因为多个线程的写操作会互相覆盖
// 正确示范1:使用std::atomic
// 对于简单的计数或累加,std::atomic通常是更好的选择,开销比mutex小
std::atomic<long long> atomic_sum = 0;
std::for_each(std::execution::par, data.begin(), data.end(), [&](int x) {
atomic_sum += x; // std::atomic<T>::operator+= 是原子操作
});
std::cout << "Atomic Sum: " << atomic_sum.load() << std::endl;
// 正确示范2:使用std::reduce (更推荐的并行求和方式)
// std::reduce就是为并行求和/归约设计的,它内部处理了同步
long long sum_reduce = std::reduce(std::execution::par, data.begin(), data.end(), 0LL);
std::cout << "Reduce Sum: " << sum_reduce << std::endl;
// 如果非要用mutex,虽然对于累加不是最佳选择,但演示其用法
long long sum_mutex = 0;
std::mutex mtx;
std::for_each(std::execution::par, data.begin(), data.end(), [&](int x) {
std::lock_guard<std::mutex> lock(mtx); // 每次修改前加锁
sum_mutex += x;
});
std::cout << "Mutex Sum: " << sum_mutex << std::endl;
return 0;
}非线程安全的容器:
std::vector
std::map
std::vector
push_back
std::map
concurrent_vector
全局变量或静态变量: 任何对全局或静态变量的读写操作,只要涉及多个线程,都必须小心。
解决方案:
std::reduce
std::atomic
std::atomic
std::mutex
std::mutex
std::lock_guard
std::unique_lock
以上就是C++并行算法 C++17执行策略解析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号