C++并行计算之OpenMP多线程

OpenMP 是一个用于在多个处理器上同时执行 C、C++ 和 Fortran 代码的库。如果它使用大量循环并利用 CPU 的全部功能,这会使您的代码更快。

OpenMP 是一组编译器指令、库过程和环境变量,于 1997 年开发用于为具有共享内存的系统(SMP 计算机)创建多线程应用程序。该工具最初是为 Fortan 设计的,后来包括 C 和 C++也是。如今,最流行的 C/C++ 编译器都支持 OpenMP:gcc、icc、PGI 编译器。(请注意,OpenMP 不是该词典型意义上的任何类型的库。它不会直接影响输出的二进制文件。)

重要的是要了解 OpenMP 仅在编译时工作——它将预处理器指令转换为本机 Fortran/C/C++ 代码,然后编译为可执行文件。

安装

在 Ubuntu / Linux 上设置 OpenMP

安装:sudo apt-get install libomp-dev

C++并行计算之OpenMP多线程_第1张图片

下面执行测试的过程中别忘了导入-fopenmp库

g++ demomps.cpp -fopenmp -o mp && ./mp

安装玩之后使用下面程序测试是否安装成功,也就是是否支持:

g++ demomps.cpp -g -pthread -Wno-format -fpermissive -fopenmp -o mp && ./mp

#include 
using namespace std;

int main()
{

#if _OPENMP
        cout << " support openmp \n" << endl;
#else
        cout << " not support openmp \n" << endl;
#endif
        return 0;
}

输出结果:说明已经安装成功
C++并行计算之OpenMP多线程_第2张图片

pragma omp parallel for代码示例

#pragma omp parallel for是OpenMP中的一个指令,表示接下来的for循环将被多线程执行,另外每次循环之间不能有关系。

编译制导指令以#pragma omp 开始,后边跟具体的功能指令,格式如:#pragma omp 指令[子句[,子句] …]。常用的功能指令如下:

parallel:用在一个结构块之前,表示这段代码将被多个线程并行执行;
for:用于for循环语句之前,表示将循环计算任务分配到多个线程中并行执行,以实现任务分担,必须由编程人员自己保证每次循环之间无数据相关性;
parallel for:parallel和for指令的结合,也是用在for循环语句之前,表示for循环体的代码将被多个线程并行执行,它同时具有并行域的产生和任务分担两个功能;
sections:用在可被并行执行的代码段之前,用于实现多个结构块语句的任务分担,可并行执行的代码段各自用section指令标出(注意区分sections和section);
parallel sections:parallel和sections两个语句的结合,类似于parallel for;
single:用在并行域内,表示一段只被单个线程执行的代码;
critical:用在一段代码临界区之前,保证每次只有一个OpenMP线程进入;
flush:保证各个OpenMP线程的数据影像的一致性;
barrier:用于并行域内代码的线程同步,线程执行到barrier时要停下等待,直到所有线程都执行到barrier时才继续往下执行;
atomic:用于指定一个数据操作需要原子性地完成;
master:用于指定一段代码由主线程执行;
threadprivate:用于指定一个或多个变量是线程专用,后面会解释线程专有和私有的区别。

相应的OpenMP子句为:

private:指定一个或多个变量在每个线程中都有它自己的私有副本;
firstprivate:指定一个或多个变量在每个线程都有它自己的私有副本,并且私有变量要在进入并行域或任务分担域时,继承主线程中的同名变量的值作为初值;
lastprivate:是用来指定将线程中的一个或多个私有变量的值在并行处理结束后复制到主线程中的同名变量中,负责拷贝的线程是for或sections任务分担中的最后一个线程; 
reduction:用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的归约运算,并将结果返回给主线程同名变量;
nowait:指出并发线程可以忽略其他制导指令暗含的路障同步;
num_threads:指定并行域内的线程的数目; 
schedule:指定for任务分担中的任务分配调度类型;
shared:指定一个或多个变量为多个线程间的共享变量;
ordered:用来指定for任务分担域内指定代码段需要按照串行循环次序执行;
copyprivate:配合single指令,将指定线程的专有变量广播到并行域内其他线程的同名变量中;
copyin:用来指定一个threadprivate类型的变量需要用主线程同名变量进行初始化;
default:用来指定并行域内的变量的使用方式,缺省是shared。

参考来自链接:https://blog.csdn.net/u011808673/article/details/80319792

假设您有一个很棒的程序,可以打印出 10 个数字的列表:

void noopenmp()
{
  for(int i=0;i<10;i++)
  {
    printf("noopenmp:%i\n",i);
  }

}

这会输出如下内容:

顺序打印出 10 个数字的列表:
noopenmp:0
noopenmp:1
noopenmp:2
noopenmp:3
noopenmp:4
noopenmp:5
noopenmp:6
noopenmp:7
noopenmp:8
noopenmp:9

现在,让OpenMP初始化它!

这里多了一个关键字 for, 起作用编程将 for 循环中的内容分开交给各个线程去处理.

void openmptest()
{
#pragma omp parallel for
  for(int i=0;i<10;i++){
    printf("openmptest:%i\n",i);
  }
}

这会输出如下内容:

使用 OpenMP打印出 10 个数字的列表:
openmptest:9
openmptest:5
openmptest:4
openmptest:6
openmptest:8
openmptest:7
openmptest:3
openmptest:2
openmptest:1
openmptest:0

这些数字是乱序的,因为循环中的每次迭代都是在稍微不同的时间并行执行的。

哈哈哈,等等,什么?“怎么会这么容易?” 我听你说。如果您的编译器支持 OpenMP,实际上就是这么简单。一般来说,最新版本的 GCC 应该没问题。如果您的编译器不支持它 - 编译指示将被忽略!而且您的代码会退回到单核缓慢。所以 OpenMP 完全兼容任何机器。

完整代码:

#include 
#include
#include
#include 
#include 
#include 
#include "complex.h"
#include "time.h"


//终端运算指令:g++ demomps.cpp -fopenmp -o mp && ./mp

using namespace std;


void noopenmp()
{
  for(int i=0;i<10;i++)
  {
    printf("noopenmp:%i\n",i);
  }

}


void openmptest()
{
#pragma omp parallel for
  for(int i=0;i<10;i++){
    printf("openmptest:%i\n",i);
  }
}


int main()
{

    cout<<"顺序打印出 10 个数字的列表:"<< endl;
    noopenmp();
 
    cout<<"使用 OpenMP打印出 10 个数字的列表:"<< endl;
    openmptest();

    return 0;
}

在分配线程数上的使用

#include 
#include 

int main(int argc, char** argv)
{
    omp_set_num_threads(3); // 分配线程数
    #pragma omp parallel
    {
        int ID = omp_get_thread_num();
        printf("Hello %d\n", ID);
    }
    return 0;
}

输出结果:

Hello 0
Hello 1
Hello 2

此外,pragma omp parallel for 默认使用多少线程主要与你的机器有关我的机器是12线程的,也就是电脑一共12个 Core.
C++并行计算之OpenMP多线程_第3张图片
因此,pragma omp parallel for 默认使用12,代码如下:

#include 
using namespace std;  

int main()
{
    #pragma omp parallel 
    {
        std::cout << "Hello World \n" << std::endl;
    }
    return 0;
}

输出结果:

Hello World 
Hello World 
Hello World 
Hello World 
Hello World 

Hello World 

Hello World 

Hello World 

Hello World 





Hello World 

Hello World 

Hello World 

#pragma omp parallel通过定义代码块创建多线程,如下面的方式指定哪部分代码创建多线程,同上:

#include 
#include 
int main(){
    printf("The output:\n");
    #pragma omp parallel     /* define multi-thread section */
    {
        printf("Hello World\n");
    }
    /* Resume Serial section*/
    printf("Done\n");
}

输出结果:

The output:
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World
Done

此外,也可以记录线程编号:

#include
#include

int main(){
#pragma omp parallel for 
		for (int i = 0; i < 20; i++) 
        {
			printf("this is No.%d Thread ,i=%d\n", omp_get_thread_num(), i);
		}
	return 0;
}

输出结果:

this is No.10 Thread ,i=18
this is No.11 Thread ,i=19
this is No.8 Thread ,i=16
this is No.3 Thread ,i=6
this is No.3 Thread ,i=7
this is No.7 Thread ,i=14
this is No.7 Thread ,i=15
this is No.4 Thread ,i=8
this is No.4 Thread ,i=9
this is No.2 Thread ,i=4
this is No.2 Thread ,i=5
this is No.1 Thread ,i=2
this is No.1 Thread ,i=3
this is No.0 Thread ,i=0
this is No.0 Thread ,i=1
this is No.5 Thread ,i=10
this is No.5 Thread ,i=11
this is No.9 Thread ,i=17
this is No.6 Thread ,i=12
this is No.6 Thread ,i=13

或者

#include 
#include  

int main(void)
{
    int coreNum = omp_get_num_procs();//获得处理器个数
    printf(" Core Num is %d \n", coreNum);
    #pragma omp parallel
    {int k = omp_get_thread_num();//获得每个线程的ID
    printf("ID: %d Hello, world.\n",k);}
    return 0;
}

并行计算

并行计算要确保处理的数据没有先后顺序性.

下面部分参考来自:https://blog.csdn.net/zhongkejingwang/article/details/40350027
使用形式为:

1)#pragma omp parallel for

         for()2)#pragma omp parallel

        {//注意:大括号必须要另起一行

         #pragma omp for

          for()

        }

注意:第二种形式中并行块里面不要再出现parallel制导指令,比如写成这样就不可以:

#pragma omp parallel

        {

         #pragma omp parallel for

          for()

        }

第一种形式作用域只是紧跟着的那个for循环,而第二种形式在整个并行块中可以出现多个for制导指令。下面结合例子程序讲解for循环并行化需要注意的地方。

参考链接:https://blog.csdn.net/zhongkejingwang/article/details/40350027

#include 
#include 
#include "omp.h"
using namespace std;
int main(int argc, char **argv) {
	//设置线程数,一般设置的线程数不超过CPU核心数,这里开4个线程执行并行代码段
	omp_set_num_threads(4);
#pragma omp parallel
	for (int i = 0; i < 2; i++)
		//cout << "i = " << i << ", I am Thread " << omp_get_thread_num() << endl;
		printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
}

输出结果:

 = 0, I am Thread 0
i = 1, I am Thread 0
i = 0, I am Thread 3
i = 1, I am Thread 3
i = 0, I am Thread 2
i = 1, I am Thread 2
i = 0, I am Thread 1
i = 1, I am Thread 1

从输出结果可以看到,如果不使用for制导语句,则每个线程都执行整个for循环。所以,使用for制导语句将for循环拆分开来尽可能平均地分配到各个线程执行。将并行代码改成添加for这样之后:

#include 
#include 
#include "omp.h"
using namespace std;
int main(int argc, char **argv) {
	//设置线程数,一般设置的线程数不超过CPU核心数,这里开4个线程执行并行代码段
	omp_set_num_threads(4);
#pragma omp parallel for
	for (int i = 0; i < 6; i++)
		//cout << "i = " << i << ", I am Thread " << omp_get_thread_num() << endl;
		printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
}

输出结果:可以看到线程0执行i=0和1,线程1执行i=2和3,线程2执行i=4,线程3执行i=5。线程0就是主线程这样整个for循环被拆分并行执行了。
或者:

#include 
#include 
#include "omp.h"
using namespace std;
int main(int argc, char **argv) {
	//设置线程数,一般设置的线程数不超过CPU核心数,这里开4个线程执行并行代码段
	omp_set_num_threads(4);

#pragma omp parallel
	{
#pragma omp for
		for (int i = 0; i < 6; i++)
			printf("i = %d, I am Thread %d\n", i, omp_get_thread_num());
	}

}

输出结果一样:

i = 0, I am Thread 0
i = 1, I am Thread 0
i = 5, I am Thread 3
i = 2, I am Thread 1
i = 3, I am Thread 1
i = 4, I am Thread 2

再进一步解释openmp的使用

#include 
#include    // NEW ADD

using namespace std;

int main()
{

        #pragma omp parallel for num_threads(4) // NEW ADD
        for(int i=0; i<10; i++)
        {
        cout << "\n"<<i << endl;
        }
        return 0;
}

输出结果:

3

8

0

1


6

7
2

9

4

5

可以看到乱序了!说明我们的 OpenMP 并行起了作用。 这里只多加了两行代码,就并行化了这个任务。是不是很简洁。

解析上面新加的两行:

#include 

OpenMP 的编译头文件,包括一些常用API,像获取当前的线程id.

#pragma omp parallel for num_threads(4)

用编译原语,指定其下面的代码块将会被渲染成多线程的代码,然后再编译。这里使用的线程数为 4。

参考来自:https://zhuanlan.zhihu.com/p/61857547

参考教程:https://github.com/ilyak/openmp-tutorial

参考

https://blog.csdn.net/dcrmg/article/details/53862448
https://learn.microsoft.com/en-us/cpp/parallel/openmp/reference/openmp-directives?view=msvc-170
https://blog.csdn.net/weixin_44210987/article/details/112388379
[OpenMP] 并行计算入门 https://www.cnblogs.com/aoru45/p/10075593.html

你可能感兴趣的:(Linux,ubuntu,C++使用技巧,c++,开发语言,c语言)