最小流水线必须包含输入、中间、输出三类过滤器,输入须用serial_in_order,中间优先parallel,输出推荐serial_out_of_order,且需按序add_filter()。

怎么用 tbb::pipeline 搭建基础流水线
直接能跑的最小流水线必须包含三部分:输入过滤器、中间过滤器(可多个)、输出过滤器,且至少一个过滤器得是 serial_in_order 或 parallel 类型。漏掉类型标记或没设输入端,tbb::pipeline::run() 会静默失败或崩溃。
实操建议:
- 输入过滤器必须用
tbb::filter::serial_in_order,哪怕你只是读文件行——TBB 要求数据按序进入流水线,否则后续串行阶段行为不可控 - 中间计算过滤器优先用
tbb::filter::parallel,但注意:每个并行过滤器内部不能共享未保护的全局状态,比如直接 ++ 一个全局计数器 - 输出过滤器推荐
tbb::filter::serial_out_of_order,除非你需要结果严格按输入顺序写入磁盘 - 构造
tbb::pipeline后必须调用add_filter()按执行顺序添加,顺序错会导致数据流断裂
示例片段:
tbb::pipeline p;
p.add_filter(*new tbb::filter(tbb::filter::serial_in_order, [](void*){
static int i = 0;
return i++ < 10 ? new int(i) : nullptr; // 输入:生成 0~9
}));
p.add_filter(*new tbb::filter(tbb::filter::parallel, [](void* x){
int* val = static_cast<int*>(x);
*val = *val * 2; // 并行处理
return x;
}));
p.add_filter(*new tbb::filter(tbb::filter::serial_out_of_order, [](void* x){
int* val = static_cast<int*>(x);
printf("got %d\n", *val); // 输出
delete val;
return nullptr;
}));
p.run(4); // 启动,buffer size 设为 4
为什么 tbb::filter 构造后要手动 delete
TBB 不管理 filter 对象生命周期,tbb::pipeline::add_filter() 只做浅拷贝,内部存储的是原始指针。如果你用栈对象或智能指针传入,运行时大概率 crash —— 因为 pipeline 运行完也不会析构它,而栈对象早已销毁。
立即学习“C++免费学习笔记(深入)”;
常见错误现象:
- 程序在
run()返回后立刻段错误 - 输出乱码或重复打印,其实是野指针访问
- Valgrind 报 “invalid read of size X”
正确做法只有一种:用 new 分配,让 pipeline 拿走裸指针,最后自己确保在 pipeline 生命周期结束后 delete。别试图用 std::unique_ptr 包一层传进去,TBB 不认。
怎么控制流水线吞吐与延迟平衡
tbb::pipeline::run(n) 的参数 n 是最大并发 token 数,不是线程数。它决定最多几个数据项同时在流水线里“流动”。设太小(如 1)等于强制串行;设太大(如 1000)可能压垮内存或引发缓存颠簸,尤其当某个 filter 分配大对象时。
使用场景参考:
- IO 密集型(如读文件 + 解析 JSON + 写 DB):设
n = 2~4,避免磁盘/网络队列过载 - CPU 密集型(如图像卷积 + 特征提取):设
n ≈ std::thread::hardware_concurrency(),但不超过 16,再高收益极低 - 混合型(如日志解析 + 正则匹配 + 网络上报):从 4 开始压测,看 CPU 利用率和 end-to-end 延迟拐点
注意:run() 是阻塞调用,返回时机是所有输入耗尽且所有 token 流出流水线。如果输入 filter 返回 nullptr 太早,后面 filter 可能根本没跑起来。
遇到 undefined reference to `tbb::interface9::internal::start_for... 怎么办
这是典型的链接时 TBB 库版本不匹配。你编译用了 tbb12,但链接时找到的是系统自带的 tbb4(Ubuntu 20.04 默认),或者反过来。错误信息里带 interface9 就说明头文件和库 ABI 不一致。
快速验证和修复步骤:
- 查头文件路径:
g++ -E -x c++ /dev/null | grep tbb,确认包含的是你期望的tbb/tbb.h位置 - 查链接库:
ldd your_binary | grep tbb,看实际加载的 so 文件名和路径 - 强制指定库路径:
-L/path/to/tbb/lib/intel64/gcc4.8 -ltbb,别依赖-ltbb自动查找 - 静态链接更稳:
-ltbb_static,但需确保libtbb_static.a存在且架构匹配(x86_64 vs aarch64)
Mac 上还容易遇到 @rpath/libtbb.dylib 找不到,得加 -Wl,-rpath,/path/to/tbb/lib。
流水线真正难调的从来不是语法,而是 filter 之间隐含的数据依赖和内存生命周期——比如中间 filter 把 raw pointer 传给下一个,结果自己先 delete 了;或者用 std::string 成员却忘了它可能被移动走,下个 filter 访问时已失效。


















