execution policy是c++17引入的一种机制,用于控制stl算法的执行方式,主要分为1. std::execution::seq(串行),2. std::execution::par(并行),3. std::execution::par_unseq(并行+向量化);使用时将policy作为第一个参数传入支持的算法如sort、transform等即可启用并行化加速,适用于数据量大、处理独立、易拆分的场景,但需注意线程安全、小数据性能开销、平台支持及执行顺序不可依赖等问题。
在C++17中引入了execution policy(执行策略),让STL算法具备了并行化的能力。通过简单的参数调整,就可以让一些常用的STL算法(如for_each、transform、sort等)自动使用多线程来加速处理大量数据。这对于需要高性能计算的场景非常有用。
什么是execution policy?
execution policy是一种告诉STL算法如何执行的方式。它定义在头文件
- std::execution::seq:保证算法不会以并行或向量化方式执行。
- std::execution::par:允许算法在多个线程上并行执行。
- std::execution::par_unseq:除了支持并行,还允许向量化执行(比如SIMD指令)。
这些策略可以作为第一个参数传给支持它们的STL算法。
如何用execution policy加速STL算法?
要使用并行策略,只需在调用STL算法时加上对应的policy参数即可。例如:
#include <algorithm> #include <vector> #include <execution> std::vector<int> data = /* 初始化大量数据 */; // 使用并行排序 std::sort(std::execution::par, data.begin(), data.end());
这种方式适用于很多常用算法,比如:
- std::sort
- std::transform
- std::reduce
- std::for_each
- std::copy_if
- std::count_if
只要你的编译器支持C++17及以上,并且算法实现支持并行策略,就可以直接启用。
并行化的注意事项
虽然加个policy就能并行听起来很爽,但有几个关键点需要注意:
- 线程安全:如果你的函数对象(比如Lambda表达式)有共享状态或者修改共享变量,必须自己加锁或使用原子操作,否则会引发竞态条件。
- 性能不总是提升:小数据量下开启并行反而可能因为线程调度带来额外开销。这时候用seq更合适。
- 并非所有平台都支持:某些标准库实现(如MSVC的STL)支持较好,而有些(如libstdc++早期版本)可能只提供接口但没有真正并行。
- 不要依赖执行顺序:使用par后,元素的处理顺序是不确定的,不能依赖特定顺序进行逻辑判断。
举个例子,下面这段代码如果lambda里修改了共享变量而不加锁,就可能出问题:
int sum = 0; std::for_each(std::execution::par, v.begin(), v.end(), [&](int x) { sum += x; // 这里会有数据竞争! });
正确做法应该是使用原子变量,或者改用std::reduce(它内部已经处理了合并逻辑):
int total = std::reduce(std::execution::par, v.begin(), v.end());
哪些情况下适合用并行STL?
- 数据量大,比如处理几万个以上元素;
- 每个元素的处理相对独立,不需要频繁同步;
- 算法本身容易拆分,比如排序、映射、归约等;
- 对响应时间敏感的应用,比如图像处理、数据分析、游戏物理模拟等。
如果是简单循环且没有复杂依赖,也可以考虑用OpenMP或手动线程池,但用execution policy的好处是写法简洁,兼容性也不错。
基本上就这些。掌握好execution policy的使用,可以在不少场景下轻松提升程序性能,但也要注意适用范围和潜在问题。