问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

OpenMP入门:轻松实现多线程并行计算

创作时间:
作者:
@小白创作中心

OpenMP入门:轻松实现多线程并行计算

引用
CSDN
1.
https://blog.csdn.net/weixin_44491423/article/details/127580150

OpenMP(Open Multi-Processing)是一种支持多线程并行编程的API,主要用于共享内存系统的并行计算。它通过在源代码中添加编译指令(pragma)来实现并行化,使得程序员可以方便地利用多核处理器的计算能力。本文将从多线程的基础概念开始,逐步介绍OpenMP的基本使用方法和一些高级特性。

多线程

在讨论OpenMP之前,我们先来了解一下为什么需要多线程。多线程编程的主要目的是利用现代CPU的多核特性来提高程序的执行效率。一个CPU核心可以看作是一个计算单元,如果程序是串行执行的,那么一次只能使用一个核心。而多线程编程可以让程序同时使用多个核心进行计算,从而显著提高程序的运行速度。

此外,多线程在处理I/O操作时也非常有用。在单线程情况下,如果程序需要进行I/O操作(如读写文件、网络通信等),那么在等待I/O完成期间,CPU资源将被闲置。而使用多线程可以在I/O等待期间执行其他计算任务,提高整体的资源利用率。

OpenMP简介

OpenMP是一种支持多线程并行编程的API,主要用于共享内存系统的并行计算。它通过在源代码中添加编译指令(pragma)来实现并行化,使得程序员可以方便地利用多核处理器的计算能力。OpenMP支持C、C++和Fortran语言,具有以下特点:

  • 易用性:只需要在代码中添加少量的编译指令,就可以实现并行化。
  • 可移植性:支持多种操作系统和编译器。
  • 灵活性:可以控制并行区域的粒度,支持循环并行、任务并行等多种并行模式。
  • 兼容性:可以与串行代码无缝结合,方便进行并行化改造。

接下来,我们将通过几个简单的示例来介绍OpenMP的基本使用方法。

查看是否支持OpenMP

在使用OpenMP之前,需要确认当前的编译器是否支持OpenMP。对于GCC编译器,可以通过以下命令检查:

gcc -fopenmp -o check_openmp check_openmp.c
./check_openmp

其中check_openmp.c的内容如下:

#include <stdio.h>
int main()
{
    #if _OPENMP
        printf("support openmp\n");
    #else
        printf("not support openmp\n");
    #endif
    return 0;
}

如果输出support openmp,则表示当前编译器支持OpenMP。

Hello World

下面是一个使用OpenMP输出"Hello World"的简单示例:

#include <stdio.h>
int main(void) 
{
    #pragma omp parallel
    {
        printf("Hello, world. \n");
    }
    return 0;
}

运行结果如下(假设CPU有4个核心):

Hello, world. 
Hello, world. 
Hello, world. 
Hello, world. 

可以看到,由于没有指定线程数,OpenMP默认使用CPU核心数量的线程数。我们也可以通过num_threads参数显式指定线程数:

#include <stdio.h>
int main(void) 
{
    #pragma omp parallel num_threads(6)
    {
        printf("Hello, world. \n");
    }
    return 0;
}

循环并行化

OpenMP最常用的功能之一就是循环并行化。下面是一个简单的示例:

#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
int main(void) {
    #pragma omp parallel for
    for (int i=0; i<12; i++) {
        printf("OpenMP Test, th_id: %d\n", omp_get_thread_num());
    }
    return 0;
}

运行结果如下:

OpenMP Test, th_id: 0
OpenMP Test, th_id: 1
OpenMP Test, th_id: 2
OpenMP Test, th_id: 3
OpenMP Test, th_id: 0
OpenMP Test, th_id: 1
OpenMP Test, th_id: 2
OpenMP Test, th_id: 3
OpenMP Test, th_id: 0
OpenMP Test, th_id: 1
OpenMP Test, th_id: 2
OpenMP Test, th_id: 3

可以看到,循环被分成了多个部分,由不同的线程并行执行。

共享变量与私有变量

在并行计算中,需要特别注意变量的访问方式。OpenMP提供了privateshared关键字来控制变量的访问权限。

  • shared:所有线程共享同一个变量的副本。
  • private:每个线程都有自己的变量副本。

下面是一个使用private关键字的示例:

#include <stdio.h>
#include <omp.h>
int main (int argc, char *argv[]) {
    int th_id, nthreads;
    #pragma omp parallel private(th_id)
    {
        th_id = omp_get_thread_num();
        printf("Hello World from thread %d\n", th_id);
    }
}

运行结果如下:

Hello World from thread 0
Hello World from thread 1
Hello World from thread 2
Hello World from thread 3

可以看到,每个线程都有自己的th_id变量副本,不会相互干扰。

数据竞争与reduction

在并行计算中,多个线程同时访问和修改同一个变量时,可能会发生数据竞争(data race)。为了避免这种情况,OpenMP提供了reduction关键字来处理累加、求和等操作。

下面是一个没有使用reduction的示例:

#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
int main(void) {
    int sum = 0;
    #pragma omp parallel for
    for (int i=1; i<=100; i++) {
        sum += i;
    }
    printf("%d", sum);
    return 0;
}

运行结果可能每次都不一样,因为多个线程同时修改sum变量导致了数据竞争。

使用reduction关键字可以避免这个问题:

#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
int main(void) {
    int sum = 0;
    #pragma omp parallel for reduction(+:sum)
    for (int i=1; i<=100; i++) {
        sum += i;
    }
    printf("%d", sum);
    return 0;
}

这样可以保证每次运行结果都是正确的。

Fork-Join模型

OpenMP使用Fork-Join模型来管理线程的创建和销毁。主线程遇到并行区域时会创建一组线程(Fork),然后这些线程并行执行并行区域内的代码,最后在并行区域结束时同步并销毁线程(Join)。

在并行区域中,可以使用barrier指令让所有线程在某个点同步:

#include <stdio.h>
int main(void) 
{
    int th_id, nthreads;
    #pragma omp parallel private(th_id)
    {
        th_id = __builtin_omp_get_thread_num();
        printf("Hello World from thread %d\n", th_id);
        #pragma omp barrier
        if (th_id == 0) {
            nthreads = __builtin_omp_get_num_threads();
            printf("There are %d threads\n", nthreads);
        }   
    }
    return 0;
}

运行结果如下:

Hello World from thread 0
Hello World from thread 1
Hello World from thread 2
Hello World from thread 3
There are 4 threads

可以看到,所有线程都在barrier处同步,然后主线程输出线程数量。

混合并行编程

OpenMP主要用于单节点的并行计算,对于大规模分布式系统,通常会结合MPI(Message Passing Interface)使用,形成混合并行编程模型:

  • OpenMP用于每个节点上的计算密集型任务
  • MPI用于节点之间的通信和数据共享

这种混合模型可以充分利用集群的计算资源,实现更大规模的并行计算。

参考文献

  • OpenMP中文教程 —— binzjut
  • OpenMP 入门于实例分析——jdtang

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号