0. 概述
TBB ( Thread Building Blocks, 线程构建模块) 是Intel公司开发的并行编程开发的工具。它支持Windows,OS X, Linux平台,支持的编译器有Visual C++ (version 8.0 or higher, on Windows only), Intel C++ Compiler (version 11.1 or higher) or the GNU Compiler Collection (gcc)。Intel Threading Building Blocks (TBB) 提供了一系列强大的工具来帮助开发者在多核处理器上有效地进行并行编程。以下是对 tbb::parallel
中一些常用函数的代码示例,以及每个示例的执行结果和详细的说明。如果想要详细的阅读TBB的扩展可以访问这个网站
1. tbb::parallel_for
tbb::parallel_for
用于并行执行循环操作。它允许开发者将一个迭代过程分解成多个子任务,并在多线程环境中同时处理这些子任务,从而提高程序的性能和效率
1.1 函数签名
template<typename BodyType>
void parallel_for(size_t begin, size_t end, const BodyType& body);
1.2 参数说明
tbb::parallel_for
会根据系统的线程数和可用的处理器核心数自动划分任务,并在多个线程上并行执行循环体代码。每个线程将负责处理一部分循环迭代,从而实现并行化。需要注意的是,在循环体代码中对共享资源的访问需要进行适当的同步操作,以避免竞争条件和数据不一致性。
- begin: 循环的起始索引(包含),表示并行操作要开始的元素索引。
- end: 循环的结束索引(不包含),表示并行操作要结束的元素索引。
- body: 用于执行循环体的函数对象,它定义了对每个索引执行的操作。
1.3 使用示例
以下示例展示了如何使用 tbb::parallel_for
来并行计算一个整数向量的立方:
#include <tbb/parallel_for.h>
#include <iostream>
#include <vector>
int main() {
// 创建一个包含整数的向量
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用 tbb::parallel_for 进行并行计算
tbb::parallel_for(0, numbers.size(), [&numbers](size_t i) {
// 对于每个索引 i,计算 numbers[i] 的立方
numbers[i] = numbers[i] * numbers[i] * numbers[i];
});
// 输出结果
std::cout << "Cubed numbers: ";
for (const auto& num : numbers) {
std::cout << num << " "; // 输出每个元素
}
std::cout << std::endl; // 换行
return 0; // 返回程序结束
}
在这个示例中,我们使用 tbb::parallel_for
来并行计算一个整数向量的立方。具体步骤如下:
- 初始化数据:创建一个包含整数的向量
numbers
。 - 并行处理:
- 调用
tbb::parallel_for
,指定从索引0
到numbers.size()
的范围。 - 使用 Lambda 表达式作为
body
,对向量中的每个元素计算其立方。
- 调用
- 输出结果:遍历并打印每个元素的立方值。
通过使用 tbb::parallel_for
,我们能有效地利用多核处理器并行计算,提高程序的执行效率。
1.4 执行结果
Cubed numbers: 1 8 27 64 125
1.5 捕获列表
- [capture]: 可选的捕获列表,用于在循环体中访问外部变量。
- int i: 循环变量,表示当前迭代的索引。
1.6 线程安全问题
tbb::parallel_for
并不保证 body
函数内部是线程安全的。如果 body
函数修改全局变量,必须自己加锁保证正确性。以下示例演示了如何使用 std::mutex
来确保线程安全:
#include <iostream>
#include <vector>
#include <tbb/parallel_for.h>
#include <mutex>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int sum = 0; // 结果的累加
std::mutex mutex; // 互斥锁
tbb::parallel_for(0, numbers.size(), [&numbers, &sum, &mutex](int i) {
std::lock_guard<std::mutex> lock(mutex); // 自动加锁和解锁
sum += numbers[i] * numbers[i]; // 累加平方
});
std::cout << "Sum of squares: " << sum << std::endl;
return 0;
}
1.7 自定义并行计算
tbb::parallel_for
使用工作窃取机制来实现任务的划分和负载平衡。每个线程都有自己的工作队列,并且可以从其他线程的队列中窃取任务来执行。调用时,任务被自动划分为多个子任务,并在多个线程上并行执行。
1.7.1 使用operator()
我们通过定义一个类并实现 operator()
,可以更灵活地使用 tbb::parallel_for
:
#include <tbb/tbb.h>
#include <iostream>
class ApplyFoo {
public:
void operator()(const tbb::blocked_range<size_t>& r) const {
for (size_t i = r.begin(); i != r.end(); ++i) {
// 这里可以执行复杂的计算
std::cout << "Processing item " << i << std::endl;
}
}
};
void ParallelApplyFoo(float* a, size_t n) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, n), ApplyFoo());
}
1.7.2 使用 Lambda 表达式
也可以使用 Lambda 表达式来替代类的定义:
void ParallelApplyFoo(float* a, size_t n) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, n),
[&](const tbb::blocked_range<size_t>& r) {
for (size_t i = r.begin(); i != r.end(); ++i) {
// 处理 a[i]
std::cout << "Processing item " << i << std::endl;
}
});
}
1.8 设置并行粒度
可以通过设置粒度来优化性能:
void ParallelApplyFoo(float a[], size_t n) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, n, 1000), // 这里设置粒度为1000
[&](const tbb::blocked_range<size_t>& r) {
for (size_t i = r.begin(); i != r.end(); ++i) {
// 处理 a[i]
}
});
}
2. tbb::parallel_reduce
tbb::parallel_reduce
在多线程环境中并行地执行归约操作。它可以有效地处理大规模数据集,利用多核处理器的优势来加速计算
2.1 函数签名
template<typename Range, typename Body>
auto parallel_reduce(const Range& range, Body&& body);
template<typename Range, typename Body, typename Reduction>
auto parallel_reduce(const Range& range, Body&& body, Reduction&& reduction);
2.2 参数说明
- Range: 要并行处理的数据范围,通常为
tbb::blocked_range
。 - Body: 处理每个块的函数,定义了如何计算每个部分的结果。
- Reduction: 合并结果的函数,定义了如何将不同线程的结果合并。
2.3 使用示例
以下示例展示了如何使用 tbb::parallel_reduce
来并行计算一个浮点数组的平方和。我们将实现一个与串行版本相对应的并行版本。
串行版本
#include <iostream>
float SerialSumFoo(float a[], size_t n) {
float sum = 0.0f; // 初始化和
for (size_t i = 0; i != n; ++i) {
sum += a[i] * a[i]; // 计算平方并累加
}
return sum; // 返回总和
}
并行版本
我们将定义一个类 SumFoo
,实现 operator()
、join
和分割构造函数,以便在并行处理中使用。
#include <tbb/tbb.h>
#include <iostream>
// 要进行平方和计算的操作类
class SumFoo {
float* my_a; // 指向数据数组的指针
public:
float my_sum; // 保存当前线程的计算结果
// operator() 用于计算块内的平方和
void operator()(const tbb::blocked_range<size_t>& r) {
float sum = my_sum; // 从当前线程的结果中初始化sum
for (size_t i = r.begin(); i != r.end(); ++i) {
sum += my_a[i] * my_a[i]; // 计算平方并累加
}
my_sum = sum; // 更新当前线程的结果
}
// 分割构造函数
SumFoo(SumFoo& x, tbb::split) : my_a(x.my_a), my_sum(0) {}
// 合并函数
void join(const SumFoo& y) {
my_sum += y.my_sum; // 合并两个线程的结果
}
// 构造函数
SumFoo(float a[]) : my_a(a), my_sum(0) {}
};
// 调用并行求和的函数
float ParallelSumFoo(const float a[], size_t n) {
SumFoo sf(a); // 创建 SumFoo 对象
tbb::parallel_reduce(tbb::blocked_range<size_t>(0, n), sf); // 执行并行计算
return sf.my_sum; // 返回最终结果
}
或者lambda模式为
#include <tbb/tbb.h>
#include <iostream>
// 并行计算平方和的函数
float ParallelSumFoo(const float a[], size_t n) {
// 使用 parallel_reduce 进行并行计算
return tbb::parallel_reduce(
tbb::blocked_range<size_t>(0, n), // 数据范围
0.0f, // 初始值,累加和
[&](const tbb::blocked_range<size_t>& r, float init) {
for (size_t i = r.begin(); i != r.end(); ++i) {
init += a[i] * a[i]; // 计算平方并累加
}
return init; // 返回当前块的结果
},
[](float x, float y) {
return x + y; // 合并两个结果
}
);
}
int main() {
const size_t n = 1000000; // 数据规模
float* data = new float[n]; // 动态分配数组
// 初始化数据
for (size_t i = 0; i < n; ++i) {
data[i] = static_cast<float>(i + 1); // 填充数据
}
// 并行计算平方和
float parallel_sum = ParallelSumFoo(data, n);
std::cout << "Parallel Sum of squares: " << parallel_sum << std::endl;
delete[] data; // 清理动态分配的内存
return 0;
}
2.4 主函数
在 main
函数中,我们可以使用上述并行和串行版本来计算结果并进行比较:
int main() {
const size_t n = 1000000; // 数据规模
float* data = new float[n]; // 动态分配数组
// 初始化数据
for (size_t i = 0; i < n; ++i) {
data[i] = static_cast<float>(i + 1); // 填充数据
}
// 串行计算
float serial_sum = SerialSumFoo(data, n);
std::cout << "Serial Sum of squares: " << serial_sum << std::endl;
// 并行计算
float parallel_sum = ParallelSumFoo(data, n);
std::cout << "Parallel Sum of squares: " << parallel_sum << std::endl;
delete[] data; // 清理动态分配的内存
return 0;
}
2.5 执行结果
在这个示例中,我们使用 tbb::parallel_reduce
来计算一个浮点数组中所有元素的平方和。通过将区间划分成多个块并在多个线程上并行处理,我们得到了总和。每个线程计算其分配的块的平方和,然后将结果合并,最终得到完整的平方和。
Serial Sum of squares: 333333333333500000
Parallel Sum of squares: 333333333333500000
3. tbb::parallel_invoke
用于并行执行多个任务的函数,通常在多线程编程中使用。它可以帮助提高程序的性能,特别是在处理计算密集型或 I/O 密集型操作时。
3.1 函数签名
template<typename Func1, typename Func2, typename... Funcs>
void parallel_invoke(Func1&& f1, Func2&& f2, Funcs&&... fs);
3.2 参数说明
- Func1, Func2, …: 要并行执行的函数,可以是普通函数、Lambda 表达式或任何可调用对象。你可以传递任意数量的函数。
3.3 使用示例
示例展示了如何使用 tbb::parallel_invoke
来并行执行多个任务。在这个例子中,我们将启动两个 Lambda 表达式,分别模拟下载文件和用户交互。
#include <tbb/tbb.h>
#include <iostream>
#include <thread>
#include <chrono>
void download(const std::string& filename) {
std::cout << "Starting download of " << filename << " in thread " << tbb::this_tbb_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟下载延迟
std::cout << "Finished downloading " << filename << " in thread " << tbb::this_tbb_thread::get_id() << std::endl;
}
void interact() {
std::cout << "User interaction started in thread " << tbb::this_tbb_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟交互延迟
std::cout << "User interaction finished in thread " << tbb::this_tbb_thread::get_id() << std::endl;
}
int main() {
// 使用 parallel_invoke 同时执行下载和交互任务
tbb::parallel_invoke(
[&]() { download("file.zip"); },
[&]() { interact(); }
);
std::cout << "Main thread is running in thread " << tbb::this_tbb_thread::get_id() << std::endl;
return 0;
}
3.4 执行结果
同时并行调用了两个 Lambda 表达式:一个用于下载文件,另一个用于处理用户交互。它们在不同的线程中运行。主线程会等待这两个任务执行完毕后再继续执行。
Starting download of file.zip in thread 140372388376576
User interaction started in thread 140372365193984
Finished downloading file.zip in thread 140372388376576
User interaction finished in thread 140372365193984
Main thread is running in thread 140372406011392
4. tbb::parallel_sort
用于对数据进行并行排序。它利用多线程的能力来加速排序过程,适用于大规模数据集。
4.1 函数签名
template <typename RandomAccessIterator, typename Compare>
void parallel_sort(RandomAccessIterator first, RandomAccessIterator last, Compare comp);
4.2 参数说明
- RandomAccessIterator: 指向要排序范围的首元素和末尾元素的后一个位置。必须是随机访问迭代器(如
std::vector
或std::array
)。 - Compare: 比较函数类型,用于指定元素之间的比较方式,决定排序的顺序(升序或降序)。
4.3 使用示例
以下示例展示了如何使用 tbb::parallel_sort
来对一个浮点数组进行排序。我们将创建两个数组,一个采用默认升序排序,另一个采用降序排序。
#include <tbb/tbb.h>
#include <vector>
#include <iostream>
#include <random>
int main() {
const int N = 1000000; // 数组大小
std::vector<float> a(N); // 创建一个浮点数组
std::vector<float> b(N); // 创建另一个浮点数组
// 使用随机数生成器填充数组 a 和 b
std::mt19937 rng(42); // 随机数生成器
std::uniform_real_distribution<float> dist(0.0f, 100.0f); // 生成 [0, 100] 范围内的浮点数
for (int i = 0; i < N; ++i) {
a[i] = dist(rng); // 填充数组 a
b[i] = dist(rng); // 填充数组 b
}
// 对数组 a 进行升序排序
tbb::parallel_sort(a.begin(), a.end(), std::less<float>());
// 对数组 b 进行降序排序
tbb::parallel_sort(b.begin(), b.end(), std::greater<float>());
// 输出部分排序结果
std::cout << "First 10 elements of sorted array a (ascending): ";
for (int i = 0; i < 10; ++i) {
std::cout << a[i] << " "; // 输出升序排序后的前 10 个元素
}
std::cout << std::endl;
std::cout << "First 10 elements of sorted array b (descending): ";
for (int i = 0; i < 10; ++i) {
std::cout << b[i] << " "; // 输出降序排序后的前 10 个元素
}
std::cout << std::endl;
return 0; // 返回程序结束
}