OpenMP学习笔记2
OpenMP子句的用法
- private 子句
private 子句可以将变量声明为线程私有,声明称线程私有变量以后,每个线程都有一个该变量的副本,线程之间不会互相影响,其他线程无法访问其他线程的副本。原变量在并行部分不起任何作用,也不会受到并行部分内部操作的影响。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i = 10;
#pragma omp parallel for private(i)
//private声明的变量不会继承同名变量的值
for (i = 0; i < 4; i++)
{
printf("i = %d\n", i);
}
printf("outside i = %d\n", i);
return 0;
}
结果:
i = 0
i = 1
i = 2
i = 3
outside i = 10
- firstprivate子句
private子句不能继承原变量的值,我们可以使用firstprivate子句来实现线程私有变量继承原来变量的值。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int t = 10, i;
#pragma omp parallel for firstprivate(t)
for (i = 0; i < 4; i++)
{
t += i;
printf("t = %d\n", t);
}
printf("outside t = %d\n", t);
return 0;
}
结果:
t = 11
t = 13
t = 12
t = 10
outside t = 10
- lastprivate子句
除了在进入并行部分时需要继承原变量的值外,有时我们还需要再退出并行部分时将计算结果赋值回原变量,lastprivate子句就可以实现这个需求。
需要注意的是,根据OpenMP规范,在循环迭代中,是最后一次迭代的值赋值给原变量;如果是section结构,那么是程序语法上的最后一个section语句赋值给原变量。
如果是类(class)变量作为lastprivate的参数时,我们需要一个缺省构造函数,除非该变量也作为firstprivate子句的参数;此外还需要一个拷贝赋值操作符。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int t = 10, i;
#pragma omp parallel for firstprivate(t),lastprivate(t)
for (i = 0; i < 4; i++)
{
t += i;
printf("t = %d\n", t);
}
printf("outside t = %d\n", t);
return 0;
}
结果:
t = 12
t = 13
t = 10
t = 11
outside t = 13
- threadprivate子句
threadprivate子句可以将一个变量复制一个私有的拷贝给各个线程,即各个线程具有各自私有的全局对象。
格式:
#pragma omp threadprivate(list)
例子:
#include <stdio.h>
#include <omp.h>
int g = 0;
#pragma omp threadprivate(g)
int main(int argc, char* argv[])
{
#pragma omp parallel
{
g = omp_get_thread_num();
}
#pragma omp parallel
{
printf("thread id: %d g: %d\n", omp_get_thread_num(), g);
}
return 0;
}
结果:
thread id: 1 g: 1
thread id: 5 g: 5
thread id: 2 g: 2
thread id: 3 g: 3
thread id: 6 g: 6
thread id: 4 g: 4
thread id: 0 g: 0
thread id: 7 g: 7
- shared子句
Share子句可以将一个变量声明成共享变量,并且在多个线程内共享。需要注意的是,在并行部分进行写操作时,要求共享变量进行保护,否则不要随便使用共享变量,尽量将共享变量转换为私有变量使用。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int t = 20, i;
#pragma omp parallel for shared(t)
for (i = 0; i < 4; i++)
{
if (i % 2 == 0)
t++;
printf("i = %d, t = %d\n", i, t);
}
return 0;
}
结果:
i = 3, t = 20
i = 1, t = 20
i = 0, t = 21
i = 2, t = 22
- reduction子句
reduction子句可以对一个或者多个参数指定一个操作符,然后每一个线程都会创建这个参数的私有拷贝,在并行区域结束后,迭代运行指定的运算符,并更新原参数的值。
私有拷贝变量的初始值依赖于redtution的运算类型。
具体用法如下
reduction(operator:list)
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i, sum = 10;
#pragma omp parallel for reduction(+: sum)
for (i = 0; i < 5; i++)
{
sum += i;
printf("%d\n", sum);
}
printf("sum = %d\n", sum);
return 0;
}
结果:
1
3
0
2
4
sum = 20
- copyin子句
copyin子句可以将主线程中变量的值拷贝到各个线程的私有变量中,让各个线程可以访问主线程中的变量。
copyin的参数必须要被声明称threadprivate,对于类的话则并且带有明确的拷贝赋值操作符。
例子:
#include <stdio.h>
#include <omp.h>
int g = 0;
#pragma omp threadprivate(g)
int main(int argc, char* argv[])
{
int i;
#pragma omp parallel for
for (i = 0; i < 4; i++)
{
g = omp_get_thread_num();
printf("thread %d, g = %d\n", omp_get_thread_num(), g);
}
printf("global g: %d\n", g);
#pragma omp parallel for copyin(g)
for (i = 0; i < 4; i++)
printf("thread %d, g = %d\n", omp_get_thread_num(), g);
return 0;
}
结果:
thread 0, g = 0
thread 3, g = 3
thread 2, g = 2
thread 1, g = 1
global g: 0
thread 0, g = 0
thread 3, g = 0
thread 1, g = 0
thread 2, g = 0
OpenMP调度
- static调度子句
当parallel for没有带schedule时,大部分情况下系统都会默认采用static调度方式。假设有n次循环迭代,t个线程,那么每个线程大约分到n/t次迭代。这种调度方式会将循环迭代均匀的分布给各个线程,各个线程迭代次数可能相差1次。
用法为schedule(method)。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i;
#pragma omp parallel for schedule(static)
for (i = 0; i < 10; i++)
{
printf("i = %d, thread %d\n", i, omp_get_thread_num());
}
return 0;
}
结果:
i = 0, thread 0
i = 4, thread 2
i = 1, thread 0
i = 7, thread 5
i = 8, thread 6
i = 2, thread 1
i = 9, thread 7
i = 3, thread 1
i = 5, thread 3
i = 6, thread 4
Size参数的用法:在静态调度的时候,我们可以通过指定size参数来分配一个线程的最小迭代次数。指定size之后,每个线程最多可能相差size次迭代。可以推断出[0,size-1]的迭代是在第一个线程上运行,依次类推。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i;
#pragma omp parallel for schedule(static, 3)
for (i = 0; i < 10; i++)
{
printf("i = %d, thread %d\n", i, omp_get_thread_num());
}
return 0;
}
结果:
i = 6, thread 2
i = 0, thread 0
i = 1, thread 0
i = 2, thread 0
i = 9, thread 3
i = 3, thread 1
i = 4, thread 1
i = 5, thread 1
i = 7, thread 2
i = 8, thread 2
- dynamic调度子句
动态分配是将迭代动态分配到各个线程,依赖于运行状态来确定,所以我们无法像静态调度一样事先预计进程的分配。哪一个线程先启动,哪一个线程迭代多久,这些都取决于系统的资源和线程的调度。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i;
#pragma omp parallel for schedule(dynamic)
for (i = 0; i < 10; i++)
{
printf("i = %d, thread %d\n", i, omp_get_thread_num());
}
return 0;
}
结果:
i = 0, thread 0
i = 3, thread 3
i = 8, thread 3
i = 9, thread 7
i = 2, thread 1
i = 4, thread 4
i = 5, thread 5
i = 7, thread 0
i = 1, thread 2
i = 6, thread 6
OpenMP的API函数
- omp_get_num_procs:返回调用函数时可用的处理器数目。
函数原型:
int omp_get_num_procs(void)
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
printf("%d\n", omp_get_num_procs());
#pragma omp parallel
{
printf("%d\n", omp_get_num_procs());
}
return 0;
}
结果:
8
8
8
8
8
8
8
8
8
- omp_get_num_threads:返回当前并行区域中的活动线程个数,如果在并行区域外部调用,返回1。
函数原型:
int omp_get_num_threads(void)
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
printf("%d\n", omp_get_num_threads());
#pragma omp parallel
{
printf("%d\n", omp_get_num_threads());
}
return 0;
}
结果:
1
8
8
8
8
8
8
8
8
- omp_get_thread_num:返回当前的线程号,注意不要和之前的omp_get_num_threads混淆。
函数原型:
int omp_get_thread_num(void)
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
printf("%d\n", omp_get_thread_num());
#pragma omp parallel
{
printf("%d\n", omp_get_thread_num());
}
return 0;
}
结果:
0
0
2
1
3
4
5
6
7
- omp_set_num_threads:设置进入并行区域时,将要创建的线程个数。
函数原型:
int omp_set_num_threads(void)
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
omp_set_num_threads(4);
#pragma omp parallel
{
printf("%d of %d threads\n", omp_get_thread_num(), omp_get_num_threads());
}
return 0;
}
结果:
3 of 4 threads
0 of 4 threads
1 of 4 threads
2 of 4 threads
- omp_in_parallel:可以判断当前是否处于并行状态,1,为并行态,0为非并行态。
函数原型:
int omp_in_parallel();
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
printf("%d\n", omp_in_parallel());
omp_set_num_threads(4);
#pragma omp parallel
{
printf("%d\n", omp_in_parallel());
}
return 0;
}
结果:
0
1
1
1
1
- omp_get_max_threads:该函数可以用于获得最大的线程数量,根据OpenMP文档中的规定,这个最大数量是指在不使用num_threads的情况下,OpenMP可以创建的最大线程数量。需要注意的是这个值是确定的,与它是否在并行区域调用没有关系。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
printf("%d\n", omp_get_max_threads());
omp_set_num_threads(4);
#pragma omp parallel
{
printf("%d\n", omp_get_max_threads());
}
return 0;
}
结果:
8
4
4
4
4
- omp_set_dynamic:该函数可以设置是否允许在运行时动态调整并行区域的线程数。
函数原型:
void omp_set_dynamic(int)
当参数为0时,动态调整被禁用。
当参数为非0值时,系统会自动调整线程以最佳利用系统资源。
- omp_get_dynamic:该函数可以返回当前程序是否允许在运行时动态调整并行区域的线程数。
函数原型:
int omp_get_dynamic()
当返回值为非0时表示允许系统动态调整线程。
当返回值为0时表示不允许。
例子:
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i;
printf("%d\n", omp_get_dynamic());
omp_set_dynamic(1);
#pragma omp parallel for
for (i = 0; i < 4; i++)
{
printf("%d\n", omp_get_dynamic());
}
return 0;
}
结果:
0
1
1
1
1
OpenMP中互斥锁的用法
Openmp中有提供一系列函数来进行锁的操作,一般来说常用的函数的下面4个
void omp_init_lock(omp_lock) 初始化互斥锁
void omp_destroy_lock(omp_lock) 销毁互斥锁
void omp_set_lock(omp_lock) 获得互斥锁
void omp_unset_lock(omp_lock) 释放互斥锁
例子:
#include <stdio.h>
#include <omp.h>
static omp_lock_t lock;
int main(int argc, char* argv[])
{
int i;
omp_init_lock(&lock);
#pragma omp parallel for
for (i = 0; i < 3; ++i)
{
omp_set_lock(&lock);
printf("%d+\n", omp_get_thread_num());
printf("%d-\n", omp_get_thread_num());
omp_unset_lock(&lock);
}
omp_destroy_lock(&lock);
return 0;
}
结果:
2+
2-
1+
1-
0+
0-
omp_test_lock:用来尝试获得锁。该函数可以看作是omp_set_lock的非阻塞版本。
函数原型:bool omp_test_lock(omp_lock)
例子:
#include <stdio.h>
#include <omp.h>
static omp_lock_t lock;
int main(int argc, char* argv[])
{
int i;
omp_init_lock(&lock);
#pragma omp parallel for
for (i = 0; i < 3; ++i)
{
if (omp_test_lock(&lock))
{
printf("%d+\n", omp_get_thread_num());
printf("%d-\n", omp_get_thread_num());
omp_unset_lock(&lock);
}
else
{
printf("fail to get lock\n");
}
}
omp_destroy_lock(&lock);
return 0;
}
结果:
2+
2-
1+
1-
fail to get lock
参考: