【OpenMp】openmp库的基本语法

news2024/12/26 21:21:27

目录

  • OpenMP并行运行结构图
  • 句式
  • parallel制导命令
    • 隐式同步
  • parallel的for命令
  • parallel的for命令
    • 静态调度
    • dynamic参数
    • guided参数
  • sections制导指令
  • single制导指令
  • 解决多线程竞争
  • 临界区
  • 矩阵所有元素+1
  • 任务池
  • 同步点
  • shared和private
  • 单语句原子操作#pragma omp atomic
    • 复杂样例程序

OpenMP并行运行结构图

在这里插入图片描述

句式

在C/C++程序中,OpenMP的所有的编译指导命令都是以#pragma omp开始的,后面跟具体的功能指导命令,命令形式如下:

#pragma omp 指令 子句,子句,子句……
指令可以单独出现,子句必须出现在指令之后。

parallel制导命令

parallel制导命令表示接下来由花括号括起来的区域将创建多个线程并行执行。可以用num_threads子句来控制线程的个数,如下:

#include<omp.h>
#include<iostream>
using namespace std;
int main()
{	
#pragma omp parallel num_threads(5)
	{
		
		cout << "Hello, world!" << endl;
	}
}

也可以用此:

#include<omp.h>
#include<iostream>
using namespace std;
int main()
{
	omp_set_num_threads(2);
#pragma omp parallel
	{
		
		cout << "Hello, world!" << endl;
	}
}

omp_set_num_threads(num_threads)函数设置的是整个程序中的线程数,其作用域覆盖从调用点开始到程序结束的整个范围。也就是说,一旦设置了总线程数,它将适用于该调用点之后的所有并行区域。

然而,需要注意的是,设置总线程数并不保证在整个程序执行期间都保持不变。比如,在并行区域的内部或者其他地方,通过调用 omp_set_num_threads() 来改变总线程数,会覆盖之前的设置并应用新的值。

另外,如果在程序中存在多个 omp_set_num_threads() 函数调用,后面的调用会覆盖之前的设置,并且新的值会在之后的并行区域生效。

综上所述,omp_set_num_threads() 函数的作用域在调用点及其之后的整个程序执行期间,并且可以在程序的不同位置进行多次调用以更改总线程数。

隐式同步

在 #pragma omp parallel 域中,所有的线程会同时开始执行并行区域内的代码。当线程到达某个同步点时(通常是并行区域的结束),它们会自动停止在那个位置进行等待和同步,直到所有线程都到达同步点为止。

这种默认的同步行为可以确保并行区域中的所有线程在继续执行后续代码之前,都能够达到同一个状态。这样就避免了线程之间的竞争问题,保证了数据的一致性和正确性。

parallel的for命令

#include<omp.h>
#include<iostream>
using namespace std;
int main()
{
	omp_set_num_threads(2);
#pragma omp parallel
	{
#pragma omp for
		for(int i=0;i<4;i++)
		 cout << omp_get_thread_num() << endl;
	}
}

在给定的示例代码中,#pragma omp for没有带大括号是合法的,因为循环体只有一条语句 :
cout << omp_get_thread_num() << endl;在这种情况下,大括号可以省略。

parallel的for命令

用schedule子句进行for循环任务调度的管理

schedule子句形式

schedule(type, size)
type参数有四种:1.static, 2.dynamic, 3.guided, 4.runtime

size参数是整形数据:表示循环迭代次数划分的单位。

静态调度

不用size参数时分配给每个程序的都是n/t次连续迭代,n为迭代次数,t为并行的线程数目。

#include<omp.h>
#include<iostream>
using namespace std;
int main()
{
	omp_set_num_threads(2);
#pragma omp parallel for schedule(static)
		for(int i=0;i<8;i++)
		 cout << omp_get_thread_num() << endl;
}

dynamic参数

动态调度模式是先到先得的方式进行任务分配,不用size参数的时候,先把任务干完的线程先取下一个任务,以此类推,而不是一开始就分配固定的任务数。使用size参数的时候,分配的任务以size为单位,一次性分配size个。虽然很智能,在任务难度不均衡的时候适合用dynamic,否则会引起过多的任务动态申请的开销。

guided参数

刚开始每个线程会分配到比较大的迭代块,后来分配到的迭代块逐渐递减,没有指定size就会降到1,否则降到size。

sections制导指令

用sections把不同的区域交给不同的线程去执行.

#include<omp.h>
#include<iostream>
using namespace std;
int main()
{
	omp_set_num_threads(3);
#pragma omp parallel sections
	{
#pragma omp section
		{
			cout <<omp_get_thread_num();
		}
#pragma omp section
		{
			cout << omp_get_thread_num();
		}
#pragma omp section
		{
			cout << omp_get_thread_num();
		}
	}
}

这样,每个线程将独立执行一个部分,而不会相互干扰。每个部分的执行顺序是不确定的,可以由系统动态决定。

single制导指令

single制导指令所包含的代码段只有一个线程执行,别的线程跳过该代码,如果没有nowait子句,那么其他线程将会在single制导指令结束的隐式同步点等待。有nowait子句其他线程将跳过等待往下执行(#pragma omp single nowait)。

#pragma omp single指令用于标记只能由一个线程执行的代码块。这个代码块只会被其中一个线程执行,而其他线程会跳过这个代码块。这在需要确保只有一个线程执行某个任务时非常有用。

#include<omp.h>
#include<iostream>
using namespace std;
int main()
{
	omp_set_num_threads(4);
#pragma omp parallel
	{
#pragma omp single
		{
			cout << "single thread=" << omp_get_thread_num()<<endl;
		}
		cout << omp_get_thread_num() << endl;
	}
		
}

解决多线程竞争

#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;
}

在OpenMP中,reduction是用于对并行循环中的变量进行归约操作的指令。它能够自动将每个线程的局部变量的结果累加到一个全局变量中,从而避免了竞争条件和数据冲突。

在示例代码中,#pragma omp parallel for reduction(+:sum)指令将变量sum声明为一个归约变量,并指定了归约操作为加法+。

在并行循环中,每个线程都会拥有自己的sum的局部副本,并在循环的迭代过程中进行累加操作。当循环结束后,reduction指令将会将每个线程的sum的局部副本的结果相加,并将最终的总和存储到全局的sum变量中。这样就保证了并行计算的正确性。

在示例代码中,循环中的每个线程将自己的局部sum累加到全局sum中,最后打印出最终的总和。

请注意,reduction指令支持的操作除了加法+以外,还包括减法-、位异或^、位与&、位或|、最大值max和最小值min。你可以根据需要选择适当的归约操作来实现其他类型的归约计算。

总之,reduction指令是OpenMP中用于对并行循环中的变量进行归约操作的指令,能够自动将每个线程的局部变量的结果累加到一个全局变量中。在你的示例代码中,reduction(+:sum)将循环中的每个线程的局部sum累加到全局sum中。
也可以声明多个:

#include <stdio.h>
#include <omp.h>

int main() {
    int sum = 0;
    int count = 0;
    #pragma omp parallel for reduction(+:sum) reduction(+:count)
    for (int i = 0; i <= 100; i++) {
        sum += i;
        count++;
    }

    printf("Sum: %d\n", sum);
    printf("Count: %d\n", count);

    double average = static_cast<double>(sum) / count;
    printf("Average: %f\n", average);

    return 0;
}

临界区

判断最大值

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

int main() {
    int max = -1;

    omp_set_num_threads(20);
    
    #pragma omp parallel
    {
        int local_max = -1;

        #pragma omp for
        for (int i = 1; i <= 100; i++) {
            if (i > local_max) {
                local_max = i;
            }
        }

        #pragma omp critical
        {
            if (local_max > max) {
                max = local_max;
            }
        }
    }

    std::cout << "Max: " << max << std::endl;

    return 0;
}

#pragma omp critical是OpenMP中的一个指令,用于创建一个临界区(critical section)。

临界区是一段代码,在同一时间只能由一个线程执行。临界区中的代码是为了保护共享资源,避免竞态条件(race condition)的发生。

当一个线程进入临界区时,其他线程必须等待,直到当前线程退出临界区。这样可以确保在同一时间只有一个线程能够访问临界区中的代码,从而避免并发访问共享资源引发的问题。

在归约操作中,#pragma omp critical用于确保只有一个线程进行更新操作,以避免多个线程同时修改共享变量引发的竞态条件。

在之前的示例代码中,我们将归约操作放在了#pragma omp critical指令内部,这样每个线程在计算局部最大值后,都会尝试进入临界区,只有一个线程能够成功进入临界区并更新最大值max。这样可以保证线程安全并得到正确的最大值结果。

总结而言,#pragma omp critical用于创建临界区,确保在同一时间只有一个线程执行临界区中的代码,用于保护共享资源的并发访问。

矩阵所有元素+1

#include <iostream>
#include <opencv2/opencv.hpp>
#include <omp.h>

int main() {
    cv::Mat mat(100, 100, CV_32S);

    // 初始化矩阵
    for (int i = 0; i < mat.rows; i++) {
        for (int j = 0; j < mat.cols; j++) {
            mat.at<int>(i, j) = 0;
        }
    }

    // 并行化操作
    #pragma omp parallel for
    for (int i = 0; i < mat.rows; i++) {
        for (int j = 0; j < mat.cols; j++) {
            mat.at<int>(i, j) += 1;
        }
    }

    // 输出结果
    for (int i = 0; i < mat.rows; i++) {
        for (int j = 0; j < mat.cols; j++) {
            std::cout << mat.at<int>(i, j) << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

任务池

在这个示例中,我们使用OpenMP的#pragma omp task指令创建了一个任务池。我们使用#pragma omp parallel创建一个并行区域,并使用#pragma omp single指定只有一个线程负责添加任务。

在循环中,我们使用#pragma omp task将任务task_func(i)添加到任务池中。task_func是一个简单的函数,它接受任务ID作为参数,打印任务ID和执行任务的线程号。

通过添加任务到任务池,每个线程可以根据可用的线程资源从任务池中获取任务并执行。OpenMP运行时系统会自动负责任务的调度和执行。

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

void task_func(int task_id) {
    std::cout << "Task " << task_id << " is executed by Thread " << omp_get_thread_num() << std::endl;
}

int main() {
    const int num_tasks = 10;
    // 在代码中设置任务池的总线程数
    omp_set_num_threads(num_threads);
    // 设置任务池
    #pragma omp parallel
    {
        // 使用single制导指令限制任务添加阶段只由一个线程执行
        #pragma omp single
        {
            for (int i = 0; i < num_tasks; i++) {
                // 添加任务到任务池,不加此句均被同一线程执行
                #pragma omp task
                task_func(i);
            }
        }

        // 等待所有任务执行完毕
        #pragma omp taskwait
    }

    return 0;
}

同步点

使用 #pragma omp parallel 创建一个并行区域,其中指定了总线程数为4。在并行区域中,每个线程首先打印出自己的线程ID,然后遇到了 #pragma omp barrier。

#pragma omp barrier 会等待所有线程都到达这个同步点,然后才会继续执行后续的代码。在这个例子中,当所有线程都到达这个同步点后,每个线程会打印出 “Thread <thread_id> executing after barrier”,其中 <thread_id> 是线程的ID。

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

int main() {
    #pragma omp parallel num_threads(4)
    {
        int thread_id = omp_get_thread_num();
        std::cout << "Thread " << thread_id << " executing before barrier" << std::endl;

        #pragma omp barrier

        std::cout << "Thread " << thread_id << " executing after barrier" << std::endl;
    }

    return 0;
}

shared和private

shared子句将变量标记为共享变量,意味着并行区域中的所有线程都可以访问和修改该变量的值。private子句将变量标记为私有变量,表示每个线程都有自己的私有拷贝,线程之间的修改不会互相影响。
下面将默认shared变量声明为private,输出结果为0:

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

int main() {
    int shared_var = 0;
    #pragma omp parallel for private(shared_var)
    for (int i = 0; i < 10; i++) {
        #pragma omp atomic
            shared_var =shared_var+ i+2;
            std::cout << "Thread " << omp_get_thread_num() << ": shared_var = " << shared_var << std::endl;
    }

    std::cout << "Final shared_var = " << shared_var << std::endl;

    return 0;
}

单语句原子操作#pragma omp atomic

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

int main() {
    int shared_var = 0;

    #pragma omp parallel for
    for (int i = 0; i < 10; i++) {
        #pragma omp atomic
            shared_var =shared_var+ i+2;
            std::cout << "Thread " << omp_get_thread_num() << ": shared_var = " << shared_var << std::endl;
    }

    std::cout << "Final shared_var = " << shared_var << std::endl;

    return 0;
}

复杂样例程序

#include <stdio.h>
#include <omp.h>
 
#define THRESHOLD 9
 
int fib(int n)
{
  int i, j;
 
  if (n<2)
    return n;
    
//具体来说,firstprivate 修饰符将变量标记为“首次私有”。在并行执行过程中,它会为每个线程创建一个私有副本,并将变量的初始值作为私有副本的初始值。这样,每个线程都有自己的一份该变量的副本,且初始值与主线程相同。
  #pragma omp task shared(i) firstprivate(n) final(n <= THRESHOLD)
  i=fib(n-1);
 
  #pragma omp task shared(j) firstprivate(n) final(n <= THRESHOLD)
  j=fib(n-2);
 
  #pragma omp taskwait
  return i+j;
}
 
 
int main()
{
  int n = 30;
  omp_set_dynamic(0);
  omp_set_num_threads(4);
 
  #pragma omp parallel shared(n)
  {
     #pragma omp single
     printf ("fib(%d) = %d\n", n, fib(n));
  }
}

具体解释如下:

shared(i): 指定变量 i 为共享变量,这意味着任务可以访问和修改这个变量。

firstprivate(n): 指定变量 n 为私有变量,并将其初始值传递给任务的副本。这样每个任务都会获得一个私有的 n 副本,而不会互相影响。

final(n <= THRESHOLD): 定义任务的终止条件。如果 n 的值小于等于 THRESHOLD,则当前任务将不再创建新的子任务,作为递归的终止条件。这样可以避免过多的任务创建和调度开销。

在斐波那契数列计算中,这行指令用于创建和调度计算 fib(n-1) 的子任务。i 是一个共享变量,可以在任务中对其进行访问和修改。n 是一个私有变量,每个任务都有自己的副本,并使用传入的初始值。

通过 final(n <= THRESHOLD),当 n 的值小于等于 THRESHOLD 时,不再创建新的任务,直接进行计算。这样可以在递归深度较浅时,避免过多的任务创建和调度开销,提高效率

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/918663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【day8】驱动

作业&#xff1a;通过GPIO子系统编写LED灯的驱动&#xff0c;应用程序测试 在led驱动中设置一个定时器&#xff0c;实现底板三盏灯一秒亮一秒灭 1.找引脚 led1---->gpioz组5号引脚 led2---->gpioz组6号引脚 led3---->gpioz组7号引脚 2.加设备树节点 //led的设备树节点…

Nginx中location语法有哪些?【杭州多测师_王sir】

1、location 介绍location是Nginx中的块级指令(block directive)&#xff0c;location指令的功能是用来匹配不同的url请求&#xff0c;进而对请求做不同的处理和响应&#xff0c;这其中较难理解的是多个location的匹配顺序&#xff0c;本文会作为重点来解释和说明。开始之前先明…

【Linux】GNOME图形化界面安装

Linux下具有多种图形化界面&#xff0c;每种图形化界面具有不同的功能&#xff0c;在这里我们安装的是GNOME。 1、 挂载yum源 挂载之前首先确保使用ISO映像文件 2.挂载之前先在/mnt下面创建一个cdrom目录用来作为挂载点目录 挂载完成之后那么就要去修改yum源了 Vi /etc/yum.r…

IntelliJ IDEA maven配置,设置pom.xml的配置文件

IntelliJ IDEA项目&#xff0c;选择 文件 设置&#xff0c;弹窗 构建、执行、部署 构建工具 Maven就可以 maven配置好以后&#xff0c;在pom.xml的配置文件中就可以设置对应的jar包了&#xff0c;这样构建的时候自动需要的jar&#xff0c;在项目中导入即 需要的jar包设置在po…

数据驱动工作效率提升的5个层次—以PreMaint设备数字化平台为例

在现代工业领域&#xff0c;数据分析已成为提升工作效率和优化生产的不可或缺的工具。从描述性分析到规范性分析&#xff0c;数据分析逐步揭示了设备运行和维护的深层信息&#xff0c;帮助企业更明智地做出决策。本文将以PreMaint设备数字化平台为例&#xff0c;探讨工业数据驱…

平面设计除了PS还有哪些工具推荐

平面设计在我们的日常生活中无处不在。无论是传统媒体还是网络媒体&#xff0c;我们每天都会沉浸在大量的平面设计作品中。因此&#xff0c;我们或多或少会对设计有自己的看法。其实&#xff0c;即使是非专业人士&#xff0c;市场上也有很多平面设计软件&#xff0c;本文盘点了…

docker 搭建私有仓库和制作镜像

目录 1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 1.1 启动mysql镜像 1.2 启动owncloud镜像 1.3 浏览器访问 1.4 总结 2、安装搭建私有仓库 Harbor 2.1 下载docker-compose并赋予执行权限 2.2 磁盘挂载&#xff0c;保存harbor 2.3 修改配置文件…

最新的单机Lustre文件系统的安装

1.单机Lustre的搭建&#xff1a; 主机名IP地址内存添加的共享磁盘大小Centos7.9-test机192.168.10.30/241G20G 1.安装E2fsprogs包 下载OSS服务器所需要的包&#xff1a;E2fsprogs包只是在Ext4的原版RPM包基础上增加了对Lustre⽀持 mkdir ~/e2fsprogs && cd ~/e2fspro…

day-29 代码随想录算法训练营 回溯part5

491.递增子序列 分析&#xff1a;存在重复元素&#xff0c;求递增子序列思路&#xff1a;1.树层去重2.当 i>0 时当前位大于上一位 思路&#xff1a; 去重逻辑在每一层都需要重新创建&#xff08;每一层遍历&#xff09;&#xff0c;且不能影响到下一层递归 class Soluti…

认识SpringIOC容器

目录 一、SpringFrameWork 二、SpringIOC容器 三、SpringIoc的核心功能 一、SpringFrameWork 1.SpringFrameWork与Spring之间的关系 SpringFrameWork是SpringCould、SpringMVC等等技术的基础实现的&#xff0c;而所有的SpringCloud、SpringFrameWork、SpringMVC等等技术组…

spring之深入理解Spring框架的核心模块与功能

深入理解Spring框架的核心模块与功能 标题: 深入理解Spring框架的核心模块与功能摘要:引言:词汇解释:详细介绍:详细介绍Spring的核心模块&#xff1a;Spring Core、Beans、ContextSpring Core:Beans:Context: 注意事项:Spring数据访问模块&#xff1a;JDBC、ORM、事务管理JDBC&…

【LVS集群】

目录 一、集群概述 1.负载均衡技术类型 2.负载均衡实现方式 二、LVS结构 1.三层结构 2.架构对象 三、LVS工作模式 四、LVS负载均衡算法 1.静态负载均衡 2.动态负载均衡 五、ipvsadm命令详解 1. -A 2. -D 3. -L 4. -a 5. -d 6. -l 7. -t 8. -s 9. -r 10. -…

【IDEA配置创建类注释模板和方法模板教程】

IDEA配置创建类注释模板和方法模板教程 废话不多说直接上干货 废话不多说直接上干货 先看效果: 类: 方法: IDEA类注释模板 &#xff0c;配置步骤&#xff1b; 直接用模板: /*** description: ${description}* author: Lynn.OuYang* create: ${YEAR}-${MONTH}-${DAY} ${HOU…

实验六 u-boot-2013.01移植

【实验目的】 了解u-boot 的代码结构及移植的基本方法 【实验环境】 ubuntu 14.04发行版FS4412实验平台交叉编译工具arm-none-linux-gnueabi- 【注意事项】 实验步骤中以“$”开头的命令表示在 ubuntu 环境下执行 【实验步骤】 一、建立自己的平台 下载uboot源码 在uboo…

怎么检测UI卡顿?(线上及线下)

什么是UI卡顿&#xff1f; 在Android系统中&#xff0c;我们知道UI线程负责我们所有视图的布局&#xff0c;渲染工作&#xff0c;UI在更新期间&#xff0c;如果UI线程的执行时间超过16ms&#xff0c;则会产生丢帧的现象&#xff0c;而大量的丢帧就会造成卡顿&#xff0c;影响用…

PDF校对工具正式上线,为用户提供卓越的文档校对解决方案

为满足当下对数字化文档校对的精准需求&#xff0c;我们今日正式发布全新的PDF校对工具。经过深入的技术研发与细致的测试&#xff0c;该工具旨在为企业和个人用户带来一个高效且准确的PDF文档校对平台。 PDF校对工具的主要特性&#xff1a; 1.全面性校对&#xff1a;工具支持…

尚硅谷大数据项目《在线教育之离线数仓》笔记003

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 第8章 数仓开发之DIM层 P039 P040 P041 P042 P043 P044 P045 P046 P047 P048 第8章 数仓开发之DIM层 P039 第8章 数仓开发之DIM层 DIM层设计要点&#xff1a; &#xff08;1&a…

项目计划怎么写? 6 个步骤助你万无一失

项目计划是项目管理的关键步骤&#xff0c;如果跳过这一步&#xff0c;项目还未启动就已岌岌可危。 什么是项目计划&#xff1f; 项目计划是实现特定目标或成果的详细路线图。它是一份全面的文件&#xff0c;回答了 “谁”、“什么”、“为什么”、"如何 "和 "…

美容行业如何快速搭建自己的预约小程序?

现在&#xff0c;搭建一个专属于美容行业的预约小程序不再是只有程序员才能做到的事情了。有了一些小程序制作平台的存在&#xff0c;任何人都可以轻松地制作出自己的小程序。下面&#xff0c;我将揭秘一个快速搭建专属美容行业预约小程序的秘诀。 首先&#xff0c;登录小程序制…

wazuh--sql检测

官网&#xff1a;Virtual Machine (OVA) - Installation alternatives Wazuh(Wazuh The Open Source Security Platform)&#xff1a;是一整套基于ossec安全检测工具和EFK日志工具构成的终端安全管理工具。不管是将其分类至HIDS&#xff0c;还是EDR&#xff0c;它都是一套通过…