OpenMp并行编程

目录

  • 介绍
  • 编译
  • 用法
    • >OpenMp parallel
    • >OpenMp for
    • >OpenMp private、firstprivate、lastprivate
    • >OpenMp section
    • >OpenMp reduction
    • >OpenMp single
    • >OpenMp master
    • >OpenMp barrier
  • OpenMp的API函数

介绍

OpenMp是一种并行编程模型,旨在简化多线程编程,它给开发人员提供了一种在共享内存系统中利用多个处理器并行执行任务。

OpenMp使用指令将一段串行代码标记为并行区域,告诉编译器这段代码需要并行执行。

OpenMp和PThread区别:

  • PThread需要显式明确各个线程的行为,OpenMp只需要简单的声明一段代码通过并行的方式去执行。
  • OpenMp是采用一种fork-join的执行模式,刚开始的时候只有一个主线程,当执行到OpenMp标记的这块串行代码时,它会派生出若干个线程去并行执行任务,当执行结束后,分支线程会合,将控制流程交给主线程。

编译

包含头文件omp.h

Windows下使用vs开发时,右键项目 -》属性 -》C/C++ -》语言 -》OpenMp支持 -》选择是

用法

>OpenMp parallel

parallerl 指令用于创建一个并行域,告诉编译器这段代码我要并行执行

#include 
#include 
#include 

void Func()
{
	int length = 5;

	// num_thread表示创建几个线程执行,如果不指定就创建最大线程数
	// omp_get_thread_num()打印当前线程id
#pragma omp parallel num_threads(3)
	{
		for (int i = 0; i < length; i++)
			std::cout << i << ": " << omp_get_thread_num() << std::endl;
	}
}

int main()
{
	auto start = std::chrono::high_resolution_clock::now();

	Func();

	auto end = std::chrono::high_resolution_clock::now();

	auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

	std::cout << "执行时间:" << duration.count() << std::endl;

	system("pause");
	return 0;
}

输出:

0: 0
1: 0
2: 0
3: 0
4: 0
0: 1
1: 1
2: 1
3: 1
4: 1
0: 2
1: 2
2: 2
3: 2
4: 2
执行时间:3659

可以看到每个线程都执行了五次循环,耗时3659us

>OpenMp for

#pragma omp parallel for 告诉编译器接下来的for循环要并行执行,使用的时候需要满足以下四点:

  • 在循环的迭代器必须是可计算的并且在执行前就需要确定迭代的次数;
  • 在循环的代码块中不能包含break,return,exit;
  • 在循环的代码块中不能使用goto跳出到循环外部;
  • 迭代器只能够被for语句中的增量表达式所修改。
#include 
#include 
#include 

void Func()
{
	int length = 5;

#pragma omp parallel for num_threads(3)
	for (int i = 0; i < length; i++)
		std::cout << i << ": " << omp_get_thread_num() << std::endl;
}

int main()
{
	auto start = std::chrono::high_resolution_clock::now();

	Func();

	auto end = std::chrono::high_resolution_clock::now();

	auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

	std::cout << "执行时间:" << duration.count() << std::endl;

	system("pause");
	return 0;
}

输出:

0: 0
1: 0
4: 2
2: 1
3: 1
执行时间:2817

可以看到3个线程共执行了五次循环,耗时2817us

>OpenMp private、firstprivate、lastprivate

它们用于给每个线程创建共享变量的副本,以确保每个线程都有自己的私有变量,不会产生竞争关系。

#include 
#include 
#include 

void Func()
{
	int length = 5;
	
	std::cout << "----------------------private用法--------------------" << std::endl;
#pragma omp parallel private(length) num_threads(3)
	{
		int tid = omp_get_thread_num();

		length *= 10 * tid;

		std::cout << "线程" << tid << ": " << length << std::endl;
	}

	std::cout << "主线程: " << length << std::endl;

	std::cout << "----------------------firstprivate用法--------------------" << std::endl;
#pragma omp parallel firstprivate(length) num_threads(3)
	{
		int tid = omp_get_thread_num();

		length *= 10 * tid;

		std::cout << "线程" << tid << ": " << length << std::endl;
	}

	std::cout << "主线程: " << length << std::endl;

	std::cout << "----------------------lastprivate用法--------------------" << std::endl;
#pragma omp parallel for lastprivate(length) num_threads(3)
	for(int i = 0;i < 5;i++)
	{
		length = 5;

		length++;

		std::cout << "线程" << omp_get_thread_num() << ": " << length << std::endl;
	}

	std::cout << "主线程: " << length << std::endl;
}

int main()
{
	auto start = std::chrono::high_resolution_clock::now();

	Func();

	auto end = std::chrono::high_resolution_clock::now();

	auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

	std::cout << "执行时间:" << duration.count() << std::endl;

	system("pause");
	return 0;
}

输出:

----------------------private用法--------------------
线程0: 0
线程1: 0
线程2: 0
主线程: 5
----------------------firstprivate用法--------------------
线程0: 0
线程2: 100
线程1: 50
主线程: 5
----------------------lastprivate用法--------------------
线程0: 6
线程0: 6
线程2: 6
线程1: 6
线程1: 6
主线程: 6
执行时间:4754

可以看到,private用法中length在线程中的初始化值为0。

firstprivate选项:
关于firstprivate的信息:

  • firstprivate选项告诉编辑器私有变量在第一个循环会继承共享变量的值;
  • 这个私有的变量只会在每个线程的第一个循环继承,而不会在每个循环中继承;
  • 其使用方法于private几乎一致:#pragma omp parallel for firstprivate;

关于变量的拷贝:

  • 如果数据是基础数据类型,如int,double等,会将数据进行直接拷贝
  • 如果变量是一个数组,它会拷贝一个对应的数据以及大小到私有内存中;
  • 如果变量为指针,它会将变量指向的地址拷贝过去,指向相同地址;
  • 如果变量是一个类的实例,它会调用对应的构造函数构造一个私有的变量。

lastprivate选项告诉编辑器

  • 私有变量会在最后一个循环出去的时候,用私有变量的值替换掉我们共享变量的值;
  • 当负责最后一个iteration的线程离开循环的时候,它会将该私有变量的值赋值给当前共享变量的值。

>OpenMp section

OpenMP 的 section 是在并行代码中实现任务划分和分配的一种机制。section 用于将操作划分为多个独立的部分,这些部分可以被并行执行。

#include 
#include 
#include 

void Func()
{
	int length = 5;
	
#pragma omp parallel num_threads(3)
	{
		#pragma omp sections
		{
			#pragma omp section
			{
				int threadID = omp_get_thread_num();
				std::cout << "Thread " << threadID << ", Section 1" << std::endl;
			}

			#pragma omp section
			{
				int threadID = omp_get_thread_num();
				std::cout << "Thread " << threadID << ", Section 2" << std::endl;
			}

			#pragma omp section
			{
				int threadID = omp_get_thread_num();
				std::cout << "Thread " << threadID << ", Section 3" << std::endl;
			}
		}
	}
}

int main()
{
	auto start = std::chrono::high_resolution_clock::now();

	Func();

	auto end = std::chrono::high_resolution_clock::now();

	auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

	std::cout << "执行时间:" << duration.count() << std::endl;

	system("pause");
	return 0;
}

输出:

Thread 0, Section 1
Thread 1, Section 3
Thread 2, Section 2
执行时间:4200

通过 sections 和 section 的组合,我们可以将任务细分为多个独立的部分,让不同的线程并行地执行这些部分。这种方式可以在并行计算中提高效率,并充分利用多核处理器的性能。注意,sections 中的工作的划分是静态的,编译时就确定了各个 section 的分配情况,而不会根据实际运行时的情况动态调整。

>OpenMp reduction

reduction 是一种用于对多个线程私有变量进行合并操作的机制。它可以方便地进行诸如求和、求积、求最大值、求最小值等归约操作。

#include 
#include 
#include 

void Func()
{
	int length = 5;
	
#pragma omp parallel num_threads(3) reduction(+:length)
	{
		#pragma omp sections
		{
			#pragma omp section
			{
				int threadID = omp_get_thread_num();
				length = length + 10;
				std::cout << "Thread " << threadID << ": " << length << std::endl;
			}

			#pragma omp section
			{
				int threadID = omp_get_thread_num();
				length = length + 20;
				std::cout << "Thread " << threadID << ": " << length << std::endl;
			}

			#pragma omp section
			{
				int threadID = omp_get_thread_num();
				length = length + 30;
				std::cout << "Thread " << threadID << ": " << length << std::endl;
			}
		}
	}

	std::cout << "主线程: " << length << std::endl;
}

int main()
{
	auto start = std::chrono::high_resolution_clock::now();

	Func();

	auto end = std::chrono::high_resolution_clock::now();

	auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

	std::cout << "执行时间:" << duration.count() << std::endl;

	system("pause");
	return 0;
}

输出:

Thread 0: 10
Thread 1: 30
Thread 2: 20
主线程: 65
执行时间:1534

reduction 还支持其他操作符,如 -、*、max、min 等。

>OpenMp single

single 是 OpenMP 指令中的一个子句,它用于指定只有一个线程执行 single 区域中的代码。这个区域中的代码在并行环境下只会被一个线程执行,其他线程会跳过这部分代码。

#include 
#include 
#include 

void Func()
{
	int length = 5;
	
#pragma omp parallel num_threads(3) firstprivate(length)
	{
		#pragma omp single
		{
			length = 1;
		}
		#pragma omp barrier

		std::cout << "Thread " << omp_get_thread_num() << ": " << length << std::endl;
	}

	std::cout << "主线程: " << length << std::endl;
}

int main()
{
	auto start = std::chrono::high_resolution_clock::now();

	Func();

	auto end = std::chrono::high_resolution_clock::now();

	auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

	std::cout << "执行时间:" << duration.count() << std::endl;

	system("pause");
	return 0;
}

输出:

Thread 2: 5
Thread 0: 1
Thread 1: 5
主线程: 5
执行时间:2233

在这个例子中,single 子句定义了一个代码块,它只会由一个线程执行,在代码块中,这个线程将变量 x 的值设置为1。然后使用 barrier 指令同步所有线程,确保在输出 x 前,single 区域中的代码已经执行完毕。执行完后,每个线程输出变量 x 的值,这时只有一个线程的 x 值为 1,其他线程的 x 值为 0。

需要注意的是,single 指令只保证至多一个线程执行这部分代码,但并不能保证是哪个线程执行,也不能保证是同一个线程执行。如果需要指定具体的线程执行代码,可以使用 master 指令。

>OpenMp master

master子句告诉编译器接下来紧跟的下段代码将会会由主线程执行,它不会出现等待现象。

>OpenMp barrier

barrier 是一个 OpenMP 中的指令,用于同步并行区域中的线程。它的作用是确保在指定的并行区域内的所有线程都到达 barrier 指令之前不会继续执行下去,直到所有线程都到达 barrier 之后才会继续执行后面的代码。

barrier 指令可以用来处理需要等待其他线程执行完毕才能继续执行的情况,例如需要确保某些共享数据的一致性或者需要线程之间进行数据交换等。

在使用 barrier 指令时,需要注意以下几点:

1、barrier 指令必须在并行区域内使用,否则会被忽略。
2、所有参与并行区域的线程都必须到达 barrier 之前的位置,否则可能导致死锁或其他错误。
3、barrier 之后的代码只有在所有线程都到达 barrier 之后才会执行,因此应谨慎使用 barrier,避免出现线程之间的不平衡。
4、在嵌套的并行区域中,每个并行区域都有自己的 barrier,因此需要确保每个并行区域内的所有线程都正确地使用了 barrier。

#include 
#include 
#include 

void Func()
{
	int length = 5;
	
#pragma omp parallel num_threads(4)
	{
		int thread_num = omp_get_thread_num();
		std::cout << "Thread " << thread_num << " before barrier" << std::endl;

		#pragma omp barrier

		std::cout << "Thread " << thread_num << " after barrier" << std::endl;
	}
}

int main()
{
	auto start = std::chrono::high_resolution_clock::now();

	Func();

	auto end = std::chrono::high_resolution_clock::now();

	auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

	std::cout << "执行时间:" << duration.count() << std::endl;

	system("pause");
	return 0;
}

输出:

Thread 0 before barrier
Thread 2 before barrier
Thread 3 before barrier
Thread 1 before barrier
Thread 1 after barrier
Thread 0 after barrier
Thread 2 after barrier
Thread 3 after barrier
执行时间:2670

在这个示例中,我们创建了一个并行区域,使用了 num_threads(4) 指定了并行区域中的线程数量为4。在每个线程中,我们输出了线程号并在 barrier 前后进行了打印。运行这段代码可以看到,所有线程都会在 barrier 处等待,直到所有线程都到达 barrier,然后继续执行后面的代码。

OpenMp的API函数

函数名 函数作用
omp_set_num_threads(n) 设置 OpenMP 并行区域中的线程数量为 n
omp_get_num_threads() 获得正在运行的并行区域的线程数量。
omp_get_thread_num() 获得当前线程的线程号。
omp_get_max_threads() 获得可以用于并行执行的最大线程数。
omp_get_num_procs() 获得当前计算机中可用于并行计算的处理器数目。
omp_set_dynamic(boolean) 设置或取消运行时动态调整线程数量调度。
omp_set_nested(boolean) 启用或禁用嵌套并行执行。
omp_set_num_threads 设置后续并行域中的线程格式
omp_in_parallel 判断当前是否在并行域中

你可能感兴趣的:(笔记,c++)