我以前最爱听的一句话。
“这段循环能不能并行一下?”
听起来很合理。
像是白捡性能。
然后线上啪一下。
不是更快。
是更不稳定。
当年:想并行,要么手写线程,要么上 OpenMP
很长一段时间。
你想并行处理一段数组。
要么自己开线程。
要么用 OpenMP 的 pragma。
两种都能跑。
但都很容易把代码写得很脆。
特别是。
你一不小心写了共享状态。
C++17 的愿望:并行也许可以像调用算法一样简单
所以 C++17 给了 execution policy。
看起来像这样。
#include <algorithm>
#include <execution>
#include <vector>
std::vector<int> v;
std::sort(std::execution::par, v.begin(), v.end());
读起来很直白。
“用并行策略来 sort”。
线上啪一下:我在并行 for_each 里写了共享变量
最常见的翻车是这种。
你想算总和。
你写。
#include <algorithm>
#include <execution>
#include <vector>
int sum = 0;
std::for_each(std::execution::par, v.begin(), v.end(), [&](int x) {
sum += x; // ❌ 数据竞争
});
这在单线程时没毛病。
一并行。
sum += x 就会多线程同时改。
这叫数据竞争。
结果可能是。
总和偶尔对。
偶尔少一点。
偶尔多一点。
你最难排查的那种。
先把新词掰开:execution policy 是什么
它就是一个“提示”。
告诉算法。
你希望它以什么方式执行。
seq:按顺序。
par:可以并行。
par_unseq:可以并行,甚至可以用更激进的方式(比如向量化)。
你可以把它理解成。
“你更像是在给编译器和库一个授权”。
工程里的正确姿势:别共享状态,改用归约
如果你想做 reduce。
就用专门的算法。
#include <execution>
#include <numeric>
int sum = std::reduce(std::execution::par, v.begin(), v.end(), 0);
这里没有共享的 sum +=。
库会自己做分块。
各算各的。
最后再合并。
另一个现实:par 不一定真的并行
这点会让新人意外。
execution policy 不是“必须”。
它允许实现选择。
有的标准库实现。
默认就是顺序跑。
你写了 par。
也可能没快。
所以它更像一个接口层的统一。
不是保证的性能开关。
关键结论
并行算法的核心不是 par。
是你的回调必须是“无共享副作用”的。
小结:并行不是魔法,是把坑从线程 API 换成了函数约束
C++17 让并行看起来更容易上手。
但它也让一种错误更常见。
你以为你只是把循环换成算法。
其实你是在把代码丢进多线程世界。
先确认你没有共享写。
再谈加速。