tbb::parallel_for需显式指定迭代器类型并合理设置grainsize,避免符号错误与调度开销;lambda捕获需谨慎,禁用异常且不保证执行顺序或内存屏障。
parallel_for 是 oneTBB 最常用的任务并行接口,它把一个迭代空间(如 std::vector 的索引范围)自动切分成多个子区间,由线程池并发执行。但很多人一上来就写 tbb::parallel_for(0, n, [](int i) { /* ... */ });,结果发现没提速甚至崩溃——根本原因是默认策略下,int 范围会被当作 size_t 处理,若 n 为负或极大值(如 INT_MAX),会触发未定义行为。
tbb::blocked_range 或直接使用带类型推导的 tbb::parallel_for(tbb::make_blocked_range(0U, n), ...)
[&] 在并行中可能引发数据竞争;只读访问可用 [=],写共享变量必须加锁或改用 tbb::parallel_reduce
tbb::task_group 或启用 TBB_USE_EXCEPTIONS=1 编译宏oneTBB 不是简单按线程数均分循环次数,而是通过 tbb::blocked_range 的 grainsize 控制最小任务粒度。太小(如 grainsize=1)导致任务调度开销压倒计算收益;太大(如 grainsize=n/2)则并行度不足,无法压满 CPU。
grainsize 是 1,但实际应设为「单次迭代耗时 ≥ 1–10 μs」对应的数据量,例如遍历数组做浮点运算,可设为 std::max(1024U, n / (tbb::this_task_arena::max_concurrency() * 4))
split 或改用 tbb::parallel_for_each 配合 std::deque 手动分块tbb::blocked_range 的模板参数顺序:tbb::blocked_range 和 tbb::blocked_range 行为不同,后者可能因符号扩展出错对比 std::for_each(串行)、OpenMP 的 #pragma omp parallel for,tbb::parallel_for 的核心优势在于任务窃取(work-stealing)调度器,能动态平衡负载。但它不保证执行顺序,也不隐含内存屏障。
schedule(dynamic) 类似,但 oneTBB 的窃取发生在任务级,粒度更细、响应更快;而 OpenMP 多
std::for_each + 执行策略(如 std::execution::par_unseq)底层可能调用 oneTBB,但标准未规定,且 GCC libstdc++ 当前仍用 pthread 封装,不可移植std::atomic 外的变量,需手动加 std::atomic_thread_fence 或用 tbb::concurrent_vector 替代裸容器以下代码处理 std::vector 的就地平方,兼顾类型安全、粒度控制和异常安全:
#include#include #include void safe_square(std::vector
& v) { if (v.empty()) return; tbb::parallel_for( tbb::blocked_range (0, v.size(), 4096), [&](const tbb::blocked_range & r) { for (size_t i = r.begin(); i != r.end(); ++i) { v[i] = v[i] * v[i]; } } ); }
这里 4096 是 grainsize,适配典型 L1 cache line 大小;size_t 避免符号问题;range 构造函数第三个参数直接控制分割粒度,比在 lambda 里判断更高效。真正难的是评估 grainsize —— 它依赖硬件缓存、数据局部性、以及迭代体是否含分支预测失败,没法一劳永逸。