c++多线程学习(OpenMP、tbb)

目录

OpenMP案例一:

OpenMP案例二:

tbb案例一:

tbb案例二:

tbb案例三(并行循环)

tbb案例四(捕获外界变量)

tbb案例五(并行算法,求和)

tbb案例六(并行的数据结构)

tbb案例七(任务流) 

tbb案例八(任务组)


OpenMP案例一:

1.设置OpenMP支持

 2.代码

#include <iostream>
#include "omp.h"

using namespace std;

int main() {
	omp_set_num_threads(4);

#pragma omp parallel
	{
		//cout << "hello ,threads" << omp_get_thread_num() << endl;//cout打印格式混乱

		printf("I am Thread %d\n", omp_get_thread_num());
	}

	system("pause");
	return 0;
}

 3.代码结果

OpenMP案例二:

1.并行化代码:

#include <iostream>
#include "omp.h"

using namespace std;
#include <time.h>  
int main() {
	double dur;
	clock_t startTime, endTime;
	startTime = clock();
#pragma omp parallel for num_threads(8)
	
	for (int i = 0; i < 10000; ++i) {
		printf("i=%d,i am thread %d\n", i, omp_get_num_threads());
	}

	endTime = clock();
	dur = (double)(endTime - startTime);
	printf("Use Time:%f\n", (dur / CLOCKS_PER_SEC));
	system("pause");
	return 0;
}

2.并行化代码结果

 

tbb案例一:

tbb库安装就不说了,网上多的是

1.tbb操作代码和结果

#include <iostream>
#include "omp.h"

using namespace std;
#include <time.h>  
int main() {
	clock_t start, end;
	int k=0;
	double dur;
	start = clock();

	for (int i = 0; i < 200000000; ++i) {
		k+= i;
		k -= i * 0.9;
		k += i * 0.7;
		k -= i * 0.5;
	}
	end = clock();
	dur = (double)(end - start);
	printf("k=%d\n", k);
	printf("Use Time:%f\n", (dur / CLOCKS_PER_SEC));

	system("pause");
	return 0;
}

如果替换tbb并行模块,再运行耗时是9秒。

2.tbb代码和结果

tbb中并行要使用安全的vector才可以,否则会出现报错

#include <iostream>

#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include <tbb/concurrent_vector.h>
using namespace std;
#include <time.h>  
int main() {
	clock_t start, end;
	int k=0;
	double dur;
	start = clock();
	tbb::concurrent_vector<int> vec;
	tbb::parallel_for(0, 20000000, [&](int i) {
		vec.emplace_back(i);
	});

	//for (int i = 0; i < 200000000; ++i) {
	//	k+= i;
	//	k -= i * 0.9;
	//	k += i * 0.7;
	//	k -= i * 0.5;
	//}
	end = clock();
	dur = (double)(end - start);
	printf("k=%d\n", k);
	printf("Use Time:%f\n", (dur / CLOCKS_PER_SEC));

	for (auto& data : vec) {
		std::cout << data << " ";
	}
	system("pause");
	return 0;
}

结果,vec中是按顺序存储的

tbb案例二:

tbb对加速前后,参考文章

为什么我的tbb比串行还慢?-CSDN社区

#include <iostream>

#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include <tbb/concurrent_vector.h>
using namespace std;
#include <time.h>  
int main() {
	clock_t start, end;
	int k=0;
	double dur;
	start = clock();
	tbb::concurrent_vector<int> vec;
	//tbb::parallel_for(0, 200000, [&](int i) {
	//	k+= i;
	//	k -= i * 0.9;
	//	
	//	vec.emplace_back(i);
	//	vec.emplace_back(i*0);
	//	vec.emplace_back(i*0);
	//	k += i * 0.7;
	//	k -= i * 0.5;

	//	for (int j = 0; j < 99999; ++j) {
	//		k += 0.0001;
	//	}

	//});

	for (int i = 0; i < 200000; ++i) {
		k += i;
		k -= i * 0.9;

		vec.emplace_back(i);
		vec.emplace_back(i * 0);
		vec.emplace_back(i * 0);
		k += i * 0.7;
		k -= i * 0.5;
		for (int j = 0; j < 99999; ++j) {
			k += 0.0001;
		}
	}
	end = clock();
	dur = (double)(end - start);
	printf("k=%d\n", k);
	printf("Use Time:%f\n", (dur / CLOCKS_PER_SEC));

	//for (auto& data : vec) {
	//	std::cout << data << " ";
	//}
	system("pause");
	return 0;
}

两个循环,一个tbb一个非。注释打开替换即可。tbb时间11秒,非tbb73秒。

tbb案例三(并行循环)

#include <iostream>
#include <vector>
#include <chrono>
#include <tbb/tbb.h>

void performComputation(int i) {
    // 模拟一个耗时计算任务
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "Task " << i << " completed." << std::endl;
}

void parallelComputation() {
    int numTasks = 10;

    // 使用TBB并行循环来执行任务
    tbb::parallel_for(0, numTasks, [](int i) {
        performComputation(i);
        });
}

void sequentialComputation() {
    int numTasks = 10;

    // 顺序执行任务
    for (int i = 0; i < numTasks; ++i) {
        performComputation(i);
    }
}

int main() {
    std::cout << "Sequential computation:" << std::endl;
    auto start = std::chrono::high_resolution_clock::now();
    sequentialComputation();
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> duration = end - start;
    std::cout << "Sequential computation took " << duration.count() << " seconds." << std::endl;

    std::cout << std::endl;

    std::cout << "Parallel computation with TBB:" << std::endl;
    start = std::chrono::high_resolution_clock::now();
    parallelComputation();
    end = std::chrono::high_resolution_clock::now();
    duration = end - start;
    std::cout << "Parallel computation with TBB took " << duration.count() << " seconds." << std::endl;

    return 0;
}

上面的代码定义了两个函数:sequentialComputationparallelComputationsequentialComputation 顺序地执行一个包含10个任务的循环,并在每个任务上调用 performComputation 函数。parallelComputation 使用TBB库来并行执行同样的任务。

在主函数 main 中,首先以顺序方式调用 sequentialComputation,并计算其执行时间。然后使用TBB调用 parallelComputation,同样计算其执行时间。

 

其中

    // 使用TBB并行循环来执行任务
    tbb::parallel_for(0, numTasks, [](int i) {
        performComputation(i);
        });

[]是在 lambda 表达式中的方括号 [] 是用于指定捕获列表(Capture List)的语法。捕获列表用于指定在 lambda 函数体内部可以访问的外部变量。

捕获列表有以下几种形式:

  • []:不捕获任何外部变量。
  • [&]:通过引用捕获所有外部变量,使得 lambda 函数体内部可以修改这些变量。
  • [=]:通过值捕获所有外部变量,但是 lambda 函数体内部不能修改这些变量。
  • [var1, var2, ...]:指定特定的外部变量进行捕获,可以选择通过引用或者值进行捕获。

tbb案例四(捕获外界变量)

#include <iostream>
#include <tbb/tbb.h>

void performComputation(int i) {
    // 模拟一个耗时计算任务
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "Task " << i << " completed by thread " << std::this_thread::get_id() << std::endl;
}

int main() {
    int numTasks = 10;
    int externalVariable = 42;

    tbb::parallel_for(0, numTasks, [externalVariable](int i) {
        performComputation(i);
        std::cout << "External variable: " << externalVariable << std::endl;
        });

    return 0;
}

在上述示例中,我们定义了一个外部变量 externalVariable,其值为42。然后,我们使用TBB的并行循环 tbb::parallel_for 来执行一系列计算任务。在lambda表达式中,我们使用捕获列表 [externalVariable] 指定需要捕获的外部变量。

在lambda函数体内部,我们首先调用 performComputation 函数执行计算任务。然后,我们输出捕获的外部变量 externalVariable 的值。

运行上述代码,你将看到每个计算任务被并行地执行,输出了任务的完成信息和捕获的外部变量的值。

这个例子展示了如何在TBB中捕获外部变量,并在并行循环的每个迭代中使用它们。你可以根据需要捕获多个外部变量,并将它们用于计算任务中。

tbb案例五(并行算法,求和)

#include <iostream>
#include <vector>
#include <tbb/tbb.h>

int computeSum(const std::vector<int>& data) {
    return tbb::parallel_reduce(
        tbb::blocked_range<size_t>(0, data.size()),
        0,
        [&data](const tbb::blocked_range<size_t>& range, int partialSum) {
            for (size_t i = range.begin(); i != range.end(); ++i) {
                partialSum += data[i];
            }
            return partialSum;
        },
        [](int x, int y) {
            return x + y;
        }
    );
}

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 

    int sum = computeSum(data);
    std::cout << "Sum: " << sum << std::endl;

    return 0;
}

在上述示例中,我们有一个整数向量 data,其中包含一组数据。我们使用TBB的 tbb::parallel_reduce 并行算法来计算这些数据的总和。

tbb::parallel_reduce 中,我们指定了计算任务的范围,即整个数据集的范围。在计算任务的函数对象中,我们定义了如何在给定的范围内计算部分和。在这个例子中,我们使用了一个lambda函数作为计算任务的函数对象。

lambda函数接受一个范围对象 range,并使用循环迭代该范围内的元素,并将它们相加得到部分和。lambda函数的第二个参数 partialSum 初始值为0,表示部分和的累积值。

在lambda函数的结尾,我们返回部分和。最后,我们还提供了一个lambda函数,用于在所有部分和之间执行最终的累加操作。

运行上述代码,你将看到输出结果为总和的值。注意,由于使用了并行算法,计算总和的过程将在多个线程上并行执行,以提高性能。

这个示例展示了如何使用TBB的并行算法来高效地并行计算数据集的总和。你可以根据需要选择其他的并行算法,并将其应用于不同类型的计算任务。

对于 `tbb::parallel_reduce` 函数,有几个参数需要理解。以下是对每个参数的解释: 

1. `tbb::blocked_range<size_t>(0, data.size())`:这是定义要在并行算法中计算的数据范围的对象。在此示例中,我们使用 `blocked_range` 对象来指定从 0 到 `data.size()`(不包括 `data.size()`)的范围。

2. `0`:这是初始的部分和的值。在每个线程中,该值将被传递给计算任务的函数对象,用于累积部分和。

3. ` [&data](const tbb::blocked_range<size_t>& range, int partialSum)`:这是计算任务的函数对象,用于在指定的范围内计算部分和。这个函数对象是一个 lambda 函数,它接受一个范围对象 `range` 和当前的部分和 `partialSum`。

4. `[](int x, int y) { return x + y; }`:这是最终的累加操作的函数对象。它接受两个部分和的值 `x` 和 `y`,并返回它们的累加结果。

tbb案例六(并行的数据结构)

#include <iostream>
#include <vector>
#include <tbb/tbb.h>

void parallelAddElements(tbb::concurrent_vector<int>& concurrentVector, int numElements) {
    tbb::parallel_for(0, numElements, [&](int i) {
        concurrentVector.push_back(i);
        });
}

int main() {
    tbb::concurrent_vector<int> concurrentVector;

    int numElements = 10;

    parallelAddElements(concurrentVector, numElements);

    std::cout << "Concurrent Vector Size: " << concurrentVector.size() << std::endl;
    std::cout << "Concurrent Vector Elements: ";
    for (const auto& element : concurrentVector) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    return 0;
}

 在上述示例中,我们使用了TBB的 tbb::concurrent_vector 并行向量来存储整数元素。我们定义了一个名为 parallelAddElements 的函数,它使用TBB的并行循环 tbb::parallel_for 并行地向并行向量中添加元素。

parallelAddElements 函数中,我们使用lambda表达式指定了要执行的并行任务。在每个迭代中,lambda表达式将一个整数 i 添加到并行向量中,利用 concurrentVector.push_back(i) 安全地并行添加元素。

在主函数中,我们创建了一个 tbb::concurrent_vector<int> 对象 concurrentVector。然后,我们调用 parallelAddElements 函数来并行地向并行向量中添加一定数量的元素。

最后,我们输出并行向量的大小和元素。由于并行向量是线程安全的数据结构,多个线程可以同时访问和修改它,因此我们可以在并行环境下安全地操作它。

运行上述代码,你将看到输出结果显示并行向量的大小和元素。注意,由于使用了并行循环和并行向量,元素的添加顺序可能不是顺序的,但是最终结果应包含了所添加的所有元素。

tbb案例七(任务流) 

#include <tbb/flow_graph.h>
#include <iostream>

int main() {
    // 创建一个图对象
    tbb::flow::graph g;

    // 创建一个可以发出数据的广播节点
    tbb::flow::broadcast_node<int> inputNode(g);

    // 创建一个可以接收数据并对其执行操作的函数节点
    tbb::flow::function_node<int, int> printNode(g, 1, [](const int& n) {
        std::cout << "Received " << n << std::endl;
        return n;  // 返回接收到的数
        });

    // 创建一个可以接收数据并对其执行操作的函数节点,计算平方
    tbb::flow::function_node<int, int> squareNode(g, 1, [](const int& n) {
        std::cout << "Square of " << n << " is " << n * n << std::endl;
        return n * n;  // 返回数的平方
        });

    // 将广播节点和函数节点连接起来,这意味着当广播节点发出数据时,函数节点会接收并处理它
    tbb::flow::make_edge(inputNode, printNode);
    tbb::flow::make_edge(printNode, squareNode);

    // 让广播节点发送一些数据
    inputNode.try_put(10);
    inputNode.try_put(20);
    inputNode.try_put(30);

    // 等待所有任务完成
    g.wait_for_all();

    return 0;
}

 这个例子创建了一个广播节点(inputNode)和两个函数节点(printNodesquareNode)。广播节点可以向所有连接的节点广播数据。函数节点会接收数据并执行相应的操作。inputNode被连接到printNodesquareNode,这意味着inputNode广播数据时,printNodesquareNode会接收并处理这些数据。

为什么要用任务流,应用场景 

例如,假设你有一个大型的计算项目,其中有许多不同的计算任务需要执行,并且这些任务之间可能存在依赖关系。一些任务可能需要等待其他任务的结果才能开始执行,一些任务可能可以并行执行以提高效率。这种情况下,任务流就非常有用了。你可以创建一个任务流图,其中每个节点代表一个任务,边表示任务之间的依赖关系。然后你可以把数据“广播”到这个图中,让图自动执行任务。

例如,你可能有以下的任务:

  1. 从数据库中读取数据
  2. 对数据进行清洗
  3. 分析清洗后的数据
  4. 将分析结果写入到报告中

这四个任务可以组成一个任务流图,每个任务都是图中的一个节点。数据从第一个节点开始,经过每个节点的处理,最后到达最后一个节点。

tbb案例八(任务组)

首先说一下,任务流和任务组的区别

TBB的任务流(Task Flow)和任务组(Task Group)都是为了创建和管理并发任务,但它们有一些关键的区别:

  1. 任务流用于处理有明确依赖关系的任务。任务流中的每个节点代表一个任务,节点之间的边表示任务之间的依赖关系。任务流强调了数据的流动和处理,任务之间的数据流动以及相互依赖关系是明确定义的。

  2. 任务组则是一种更简单和更灵活的并发模型。你可以动态地添加任务到任务组,这些任务会并发执行,但是它们之间没有明确的依赖关系。任务组更侧重于任务的并行执行,而不是数据的流动。

#include <tbb/task_group.h>
#include <iostream>

int fib(int n) {
    if (n < 2) {
        return n;
    }
    else {
        int x, y;
        tbb::task_group g;

        g.run([&] {x = fib(n - 1); }); // 启动一个新任务来计算fib(n-1)
        g.run([&] {y = fib(n - 2); }); // 启动一个新任务来计算fib(n-2)

        g.wait(); // 等待所有任务完成

        return x + y;
    }
}

int main() {
    std::cout << "fib(10) = " << fib(10) << '\n';
    return 0;
}

 这个示例展示了如何使用任务组来并行地计算斐波那契数列中的两项。请注意,这个示例是为了展示任务组的用法,实际上使用并行计算斐波那契数列的效率可能并不高,因为斐波那契数列的计算涉及大量的重复计算。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江河地笑

实践是检验真理的唯一标准

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值