OpenMP

OpenMP

介绍

  1. 代码片段
#include 
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
#pragma omp parallel for
    for(int i = 0; i < 10; ++i)
    {
	    fprintf(stderr, "%d\n", i);
    }

    return 0;
};
  1. 编译
gcc -o a.out main.c -fopenmp
  1. 片段解释
code comments
#pragma omp single 开始一个单线程执行域。
#pragma omp parallel 开始一个多线程执行域。
#pragma omp for 在并行域中,将 for() {} 语句撤分为多个线程执行。
#pragma omp 在并行域中,使用单独的线程执行紧接的 for() {} 等。

常用函数

  1. 原型说明
function comments
int omp_get_num_procs(void) 返回当前可用的处理器个数。
int omp_get_num_threads(void) 返回当前并行区域中的活动线程个数,如果在并行区域外部调用,返回1。
int omp_get_thread_num(void) 返回当前的线程号。
int omp_set_num_threads(void) 设置进入并行区域时,将要创建的线程个数。
  1. 并行区域
#include 
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
    fprintf(stderr, "procs : %d\n", omp_get_num_procs());

#pragma omp parallel //指明下面大括号内部为并行区域
    {
        fprintf(stderr, "%d\n", i);   
    }

    return 0;
}
  1. 示例代码
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
    std::cout << "CPU number: " << omp_get_num_procs() << std::endl;

    std::cout << "Parallel area 1: " << std::endl;
#pragma omp parallel //指明下面大括号内部为并行区域
    {
        std::cout << "Num of threads is " << omp_get_num_threads();
        std::cout << "; This thread ID is " << omp_get_thread_num() << std::endl;
    }

    std::cout << "Parallel area 2: " << std::endl;
    omp_set_num_threads(4); // 设置成为并行区域创建4个线程
#pragma omp parallel //指明下面大括号内部为并行区域
    {
        std::cout << "Num of threads is " << omp_get_num_threads();
        std::cout << "; This thread ID is " << omp_get_thread_num() << std::endl;
    }
    return 0;
}

数据的共享与私有化

  1. 定义

除了并行区域中定义的变量、循环条件变量、private、firstprivate、lastprivate或reduction字句修饰的变量,并行区域中的所有变量都是共享的。例如:

#include 
#include  // OpenMP编程需要包含的头文件

int main()
{

    int share_a = 0; // 共享变量
    int share_to_private_b = 1; //通过private子句修饰该变量之后在并行区域内变为私有变量

#pragma omp parallel 
    {
        int private_c = 2;

#pragma omp for private(share_to_private_b) 
        for (int i = 0; i < 10; ++i) //该循环变量是私有的,若为两个线程,则一个线程执行0 <= i < 5,另一个线程执行5 <= i < 10
        {
            std::cout << i << std::endl;
        }
    }

    return 0;
}
  1. 共享与私有变量声明的方法
keyword comments
private(val1, val2, ...) 并行区域中变量val是私有的,即每个线程拥有该变量的一个拷贝,但都各自被初始化为0。
firstprivate(val1, val2, ...) private 不同的是,每个线程在开始循环时将会拷贝变量的初始化值。
lastprivate(val1, val2, ...) private 不同的是,每个线程在退出循环时将私有变量将会拷回到原变量中,一般配合 firstprivate 使用。
shared(val1, val2, ...) 声明val是共享的。
  1. 示例代码
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
    int shared_to_private = 1;

#pragma omp parallel for private(shared_to_private) 
    for (int i = 0; i < 10; ++i) 
    {
        std::cout << shared_to_private << std::endl;
    }

    return 0;
}

有效数据规约

  1. 关键字 reduction

说明对变量的操作原则,多个线程的执行结果通过reduction中声明的操作符进行计算。以加法为例 reduction (+: sum) ,假设sum的初始值为10,reduction(+: sum) 声明的并行区域中每个线程的sum初始值为0(规定),并行处理结束之后,会将sum的初始化值10以及每个线程所计算的sum值相加。

  1. 声明

reduction (operator: var1, val2, ...)

operator data type initial
+ 整数、浮点 0
- 整数、浮点 0
* 整数、浮点 1
& 整数 所有位均为 1
| 整数 0
^ 整数 0
&& 整数 1
|| 整数 0
  1. 示例
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
    int sum = 0; 

    std::cout << "Before: " << sum << std::endl;

#pragma omp parallel for reduction(+: sum) 
    for (int i = 0; i < 10; ++i) 
    {
        sum = sum + i;
        std::cout << sum << std::endl;
    }

    std::cout << "After: " << sum << std::endl;
    return 0;
}

atomic操作

  1. 声明

#pragma opm atomic 适用于对变量的复合运算和自增减运算。比如:

#pragma opm atomic
    x |= (1 << 2)
  1. 示例代码
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
    int sum = 0; 

    std::cout << "Before: " << sum << std::endl;

#pragma omp parallel for
    for (int i = 0; i < 20000; ++i) 
    {
#pragma omp atomic
        sum++;
    }

    std::cout << "After: " << sum << std::endl;
    return 0;
}

临界区同步

  1. 简介

critical与atomic的区别在于,atomic仅适用于上一节规定的两种类型操作,而且atomic所防护的仅为一句代码。critical可以对某个并行程序块进行防护。

  1. 声明
#pragma omp critical [(named)] //[]表示命名是可选
{
    //并行程序块,同时只能有一个线程能访问该并行程序块
}

比如:

#prgama omp critical (a)
a = b + c;
  1. 示例
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
    int sum = 0; 

    std::cout << "Before: " << sum << std::endl;

#pragma omp parallel for
    for (int i = 0; i < 100; ++i) 
    {
#pragma omp critical (a)
        {
          sum = sum + i;
          sum = sum + i * 2;
        }
    }

    std::cout << "After: " << sum << std::endl;
    return 0;
}

互斥锁函数

  1. 定义

类似 Mutex

  1. 原型说明
funciton comments
void opm_init_lock(omp_lock *) 初始化互斥器
void opm_destroy_lock(omp_lock *) 销毁互斥器
void opm_set_lock(omp_lock *) 获得互斥器
void opm_unset_lock(omp_lock *) 释放互斥器
void opm_test_lock(omp_lock *) 试图获得互斥器,如果获得成功返回true,否则返回false
  1. 示例代码
#include 
#include  // OpenMP编程需要包含的头文件

static omp_lock_t lock;

int main()
{
    omp_init_lock(&lock); // 初始化互斥锁

#pragma omp parallel for
    for (int i = 0; i < 5; ++i) 
    {
        omp_set_lock(&lock); //获得互斥器 
        std::cout << omp_get_thread_num() << "+" << std::endl;
        std::cout << omp_get_thread_num() << "-" << std::endl;
        omp_unset_lock(&lock); //释放互斥器
    }

    omp_destroy_lock(&lock); //销毁互斥器
    return 0;
}

事件同步机制

  1. 隐式栅障

前面所有的示例都带有隐式栅障,即并行区域中所有线程执行完毕之后,主线程才继续执行。

  1. 同步关键字
keyword function example
nowait 不等待指定的并行语句执行完成,继续执行下面的语句。 #pragma omp for nowait
barrier 显示同步,等待前面的线程结束。 #pragma omp barrier
master 声明对应的并行程序块只由主线程完成。 #pragma omp mater
section 用来指定不同的线程执行不同的部分。 #pragma omp parallel sections {} #pragma omp section {}
  1. 示例代码
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
#pragma omp parallel
    {
#pragma omp for nowait
        for (int i = 0; i < 1000; ++i) 
        {
            std::cout << i << "+" << std::endl;
        }
#pragma omp for
        for (int j = 0; j < 10; ++j) 
        {
            std::cout << j << "-" << std::endl;
        }
    }
    
    return 0;
}
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
#pragma omp parallel
    {
        for (int i = 0; i < 100; ++i) 
        {
            std::cout << i << "+" << std::endl;
        }

#pragma omp barrier
        for (int j = 0; j < 10; ++j) 
        {
         std::cout << j << "-" << std::endl;
        }
    }

    return 0;
}
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
#pragma omp parallel
    {
#pragma omp master
        {
            for (int j = 0; j < 10; ++j) 
            {
                std::cout << j << "-" << std::endl;
            }
        }

        std::cout << "This will printed twice." << std::endl;
    }

    return 0;
}
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
#pragma omp parallel sections //声明该并行区域分为若干个section,section之间的运行顺序为并行的关系
    {
#pragma omp section //第一个section,由某个线程单独完成
        for (int i = 0; i < 5; ++i) 
        {
            std::cout << i << "+" << std::endl;
        }

#pragma omp section //第一个section,由某个线程单独完成
        for (int j = 0; j < 5; ++j) 
        {
         std::cout << j << "-" << std::endl;
        }
    }

    return 0;
}

调度策略

  1. 说明

默认情况下会自动生成与CPU个数相等的线程,然后并行执行并行区域中的代码。通过对并行区域中的for循环进行特殊的声明,可以将for循环的不同部分分配给不同的线程运行。然后再通过锁同步或事件同步来实现并行区域的同步控制。

  1. 调度策略
policy comments condition
static 循环变量区域分为n等份,每个线程评分n份任务 各个cpu的性能差别不大
dynamic 循环变量区域分为n等份,某个线程执行完1份之后执行其他需要执行的那一份任务 cpu之间运行能力差异较大
guided 循环变量区域由大到小分为不等的n份,运行方法类似于 dynamic 由于任务份数比 dynamic,所以可以减少调度开销
runtime 在运行时来适用上述三种调度策略中的一种,默认是使用 static
  1. 声明

使用 ... for schedule(, n) 将并行任务按 policy 调度策略,for 循环每 n 迭代分成一个任务。

  1. 示例
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
#pragma omp parallel for schedule(static, 2) //static调度策略,for循环每两次迭代分成一个任务
    for (int i = 0; i < 10; ++i) //被分成了5个任务,其中循环0~1,4~5,8~9分配给了第一个线程,其余的分配给了第二个线程
    {
        std::cout << "Thread ID:" << omp_get_thread_num() << " Value:" << i << std::endl;
    }

    return 0;
}
#include 
#include  // OpenMP编程需要包含的头文件

int main()
{
#pragma omp parallel for schedule(dynamic, 2) //dynamic调度策略,for循环每两次迭代分成一个任务
    for (int i = 0; i < 10; ++i) //被分成了5个任务,只要有任务并且线程空闲,那么该线程会执行该任务
    {
        std::cout << "Thread ID:" << omp_get_thread_num() << " Value:" << i << std::endl;
    }

    return 0;
}

你可能感兴趣的:(系统编程,多线程)