OpenMP是一种用于共享内存并行系统的多线程程序设计方案,支持的编程语言包括C、C++和Fortran。OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。当编译器不支持OpenMP时,程序会退化成普通(串行)程序。程序中已有的OpenMP指令不会影响程序的正常编译运行。
在VS中启用OpenMP很简单,很多主流的编译环境都内置了OpenMP。在项目上右键->属性->配置属性->C/C++->语言->OpenMP支持,选择“是”即可。
OpenMP缺点:
1:作为高层抽象,OpenMp并不适合需要复杂的线程间同步和互斥的场合;
2:另一个缺点是不能在非共享内存系统(如计算机集群)上使用。在这样的系统上,MPI使用较多。
OpenMP采用fork-join的执行模式。开始的时候只存在一个主线程,当需要进行并行计算的时候,派生出若干个分支线程来执行并行任务。当并行代码执行完成之后,分支线程会合,并把控制流程交给单独的主线程。
一个典型的fork-join执行模型的示意图如下:
OpenMP编程模型以线程为基础,通过编译制导指令制导并行化,有三种编程要素可以实现并行化控制,他们分别是编译制导、API函数集和环境变量。
编译制导指令以#pragma omp 开始,后边跟具体的功能指令,格式如:#pragma omp 指令[子句[,子句] …]。常用的功能指令如下:
相应的OpenMP子句为:
除上述编译制导指令之外,OpenMP还提供了一组API函数用于控制并发线程的某些行为,下面是一些常用的OpenMP API函数以及说明:
OpenMP中定义一些环境变量,可以通过这些环境变量控制OpenMP程序的行为,常用的环境变量:
parallel制导指令用来创建并行域,后边要跟一个大括号将要并行执行的代码放在一起:
注意对于C/C++,你通常需要包含头文件
,并且是大小写敏感的。
[cpp] view plain copy
执行以上程序有如下输出:
程序打印出了4个“Test”,说明parallel后的语句被4个线程分别执行了一次,4个是程序默认的线程数,还可以通过子句num_threads显式控制创建的线程数:
[cpp] view plain copy
编译运行有如下输出:
程序中显式定义了6个线程,所以parallel后的语句块分别被执行了6次。第二行的空行是由于每个线程都是独立运行的,在其中一个线程输出字符“Test”之后还没有来得及换行时,另一个线程直接输出了字符“Test”。
使用parallel制导指令只是产生了并行域,让多个线程分别执行相同的任务,并没有实际的使用价值。parallel for用于生成一个并行域,并将计算任务在多个线程之间分配,从而加快计算运行的速度。可以让系统默认分配线程个数,也可以使用num_threads子句指定线程个数。
[cpp] view plain copy
运行输出:
上边程序指定了6个线程,迭代量为12,从输出可以看到每个线程都分到了12/6=2次的迭代量。
备注:如果for里面比较简单(执行时间短) ,不建议使用多线程并发, 因为 线程间的调度 也会比较耗时,是一个不小的开销。
[cpp] view plain copy
以上程序分别指定了2、4、8、12个线程和不使用OpenMP优化来执行一段垃圾程序,输出如下:
可见,使用OpenMP优化后的程序执行时间是原来的1/4左右,并且并不是线程数使用越多效率越高,一般线程数达到4~8个的时候,不能简单通过提高线程数来进一步提高效率。
for 循环语句中,书写是需要按照一定规范来写才可以的,即for循环小括号内的语句要按照一定的规范进行书写,for语句小括号里共有三条语句
for( i=start; i < end; i++)
i=start; 是for循环里的第一条语句,必须写成 “变量=初值” 的方式。如 i=0
i < end;是for循环里的第二条语句,这个语句里可以写成以下4种形式之一:
变量 < 边界值
变量 <= 边界值
变量 > 边界值
变量 >= 边界值
如 i>10 i< 10 i>=10 i>10 等等
最后一条语句i++可以有以下9种写法之一
i++
++i
i--
--i
i += inc
i -= inc
i = i + inc
i = inc + i
i = i –inc
例如i += 2; i -= 2;i = i + 2;i = i - 2;都是符合规范的写法。
sections和section指令的用法
section语句是用在sections语句里用来将sections语句里的代码划分成几个不同的段,每段都并行执行。用法如下:
#pragma omp [parallel] sections [子句]
{
#pragma omp section
{
代码块
}
}
先看一下以下的例子代码:
void main(int argc, char *argv)
{
#pragma omp parallel sections {
#pragma omp section
printf(“section 1 ThreadId = %d/n”, omp_get_thread_num());
#pragma omp section
printf(“section 2 ThreadId = %d/n”, omp_get_thread_num());
#pragma omp section
printf(“section 3 ThreadId = %d/n”, omp_get_thread_num());
#pragma omp section
printf(“section 4 ThreadId = %d/n”, omp_get_thread_num());
}
执行后将打印出以下结果:
section 1 ThreadId = 0
section 2 ThreadId = 2
section 4 ThreadId = 3
section 3 ThreadId = 1
从结果中可以发现第4段代码执行比第3段代码早,说明各个section里的代码都是并行执行的,并且各个section被分配到不同的线程执行。
使用section语句时,需要注意的是这种方式需要保证各个section里的代码执行时间相差不大,否则某个section执行时间比其他section过长就达不到并行执行的效果了。
上面的代码也可以改写成以下形式:
void main(int argc, char *argv)
{
#pragma omp parallel {
#pragma omp sections
{
#pragma omp section
printf(“section 1 ThreadId = %d/n”, omp_get_thread_num());
#pragma omp section
printf(“section 2 ThreadId = %d/n”, omp_get_thread_num());
}
#pragma omp sections
{
#pragma omp section
printf(“section 3 ThreadId = %d/n”, omp_get_thread_num());
#pragma omp section
printf(“section 4 ThreadId = %d/n”, omp_get_thread_num());
}
}
执行后将打印出以下结果:
section 1 ThreadId = 0
section 2 ThreadId = 3
section 3 ThreadId = 3
section 4 ThreadId = 1
这种方式和前面那种方式的区别是,两个sections语句是串行执行的,即第二个sections语句里的代码要等第一个sections语句里的代码执行完后才能执行。
用for语句来分摊是由系统自动进行,只要每次循环间没有时间上的差距,那么分摊是很均匀的,使用section来划分线程是一种手工划分线程的方式,最终并行性的好坏得依赖于程序员。
本篇文章中讲的几个OpenMP指令parallel, for, sections, section实际上都是用来如何创建线程的,这种创建线程的方式比起传统调用创建线程函数创建线程要更方便,并且更高效。
当然,创建线程后,线程里的变量是共享的还是其他方式,主线程中定义的变量到了并行块内后还是和传统创建线程那种方式一样的吗?创建的线程是如何调度的?等等诸如此类的问题到下一篇文章中进行讲解。
CRITICAL指令
目标:
格式:
#pragma omp critical [ name ] newline
structured_block
注意事项:
限制:
示例: 组内所有的线程都试图去并行执行。但是由于CRITICAL区块的存在,任何时刻最多只能有一个线程去执行自增操作。
#include
main(int argc, char *argv[]) {
int x = 0;
#pragma omp parallel shared(x)
{
#pragma omp critical
x = x + 1;
} /* end of parallel region */
}
BARRIER指令
目标:
格式:
#pragma omp barrier newline
限制:
ATOMIC指令
目标:
格式:
#pragma omp atomic newline
statement_expression
限制:
REDUCTION实例:向量点乘 (并行线程 求和)
#include
main(int argc, char *argv[]) {
int i, n, chunk;
float a[100], b[100], result;
/* Some initializations */
n = 100;
chunk = 10;
result = 0.0;
for (i=0; i < n; i++) {
a[i] = i * 1.0;
b[i] = i * 2.0;
}
#pragma omp parallel for default(shared) private(i) \
schedule(static,chunk) reduction(+:result)
for (i=0; i < n; i++) {
result = result + (a[i] * b[i]);
}
printf("Final result= %f\n",result);
}
限制条件:
OMP_SET_NUM_THREADS
目标:
格式:
#include
void omp_set_num_threads(int num_threads)
注意事项:
OMP_GET_NUM_THREADS
目标:
格式:
#include
int omp_get_num_threads(void)
注意事项及限制条件:
OMP_GET_MAX_THREADS
目标:
#include
int omp_get_max_threads(void)
注意事项及限制条件:
OMP_GET_THREAD_NUM
目标:
格式:
#include
int omp_get_thread_num(void)
注意事项及限制条件:
OMP_GET_THREAD_LIMIT
目标:
格式:
#include
int omp_get_thread_limit (void)
注意事项:
OMP_GET_NUM_PROCS
目标:
格式:
#include
int omp_get_num_procs(void)
OMP_IN_PARALLEL
目标:
格式:
#include
int omp_in_parallel(void)
注意事项及限制条件:
OMP_SET_DYNAMIC
目标:
格式:
#include
void omp_set_dynamic(int dynamic_threads)
注意事项及限制条件:
OMP_GET_DYNAMIC
目标:
格式:
#include
int omp_get_dynamic(void)
注意事项及限制条件:
OMP_SET_NESTED
目标:
格式:
#include
void omp_set_nested(int nested)
注意事项及限制条件:
OMP_GET_NESTED
目标:
格式:
#include
int omp_get_nested (void)
注意事项及限制条件:
OMP_SET_SCHEDULE
目标:
格式:
#include
void omp_set_schedule(omp_sched_t kind, int modifier)
OMP_GET_SCHEDULE
目标:
格式:
#include
void omp_get_schedule(omp_sched_t * kind, int * modifier )
OMP_SET_MAX_ACTIVE_LEVELS
目标:
格式:
#include
void omp_set_max_active_levels (int max_levels)
注意事项及限制条件:
OMP_GET_MAX_ACTIVE_LEVELS
目标:
格式:
#include
int omp_get_max_active_levels(void)
OMP_GET_LEVEL
目标:
格式:
#include
int omp_get_level(void)
注意事项及限制条件:
OMP_GET_ANCESTOR_THREAD_NUM
目标:
格式:
#include
int omp_get_ancestor_thread_num(int level)
注意事项和限制条件:
OMP_GET_TEAM_SIZE
目标:
格式:
#include
int omp_get_team_size(int level);
注意事项和限制条件:
OMP_GET_ACTIVE_LEVEL
目标:
格式:
#include
int omp_get_active_level(void);
注意事项和限制条件:
OMP_IN_FINAL
目标:
格式:
#include
int omp_in_final(void)
OMP_INIT_LOCK
OMP_INIT_NEST_LOCK
目标:
格式:
#include
void omp_init_lock(omp_lock_t *lock)
void omp_init_nest_lock(omp_nest_lock_t *lock)
注意事项及限制条件:
OMP_DESTROY_LOCK
OMP_DESTROY_NEST_LOCK
目标:
格式:
#include
void omp_destroy_lock(omp_lock_t *lock)
void omp_destroy_nest_lock(omp_nest_lock_t *lock)
注意事项及限制条件:
OMP_SET_LOCK
OMP_SET_NEST_LOCK
目标:
格式:
#include
void omp_set_lock(omp_lock_t *lock)
void omp_set_nest__lock(omp_nest_lock_t *lock)
注意事项和限制条件:
OMP_UNSET_LOCK
OMP_UNSET_NEST_LOCK
目标:
格式:
#include
void omp_unset_lock(omp_lock_t *lock)
void omp_unset_nest__lock(omp_nest_lock_t *lock)
注意事项和限制条件:
OMP_TEST_LOCK
OMP_TEST_NEST_LOCK
目标:
格式:
#include
int omp_test_lock(omp_lock_t *lock)
int omp_test_nest__lock(omp_nest_lock_t *lock)
注意事项和限制条件:
OMP_GET_WTIME
目标:
格式:
#include
double omp_get_wtime(void)
OMP_GET_WTICK
目标:
格式:
#include
double omp_get_wtick(void)
O
ot
ot
O
U
a
au
au
a
at
ato
ato
atom
atom
atomi
atomi
atomic
atomic
ATOMIC