Introduction HPC Sequential programs(串行程序) Parallel programs(并行程序) OpenMP OpenMP execution model fork - join 并行模式
只有一个master thread ,程序开始(单线程)- fork - parallel - join
OpenMP memory model All threads have access to the same **shared memory space **所有线程共享同意内存空间
OpenMP derectives 1 #pragma omp <directive> [clause[[,] clause] ... ]
#pragma omp :告诉编译器这是一个 OpenMP 指令。
:具体的并行控制指令。
clause :附加选项(子句),比如 private、reduction、schedule。
指令 parallel 表是这段代码被多个线程并行执行
1 2 3 4 #pragma omp parallel { printf ("Hello world \n" ); }
for 用于for循环之前,将循环分配到多个线程执行,必须保证每次循环之间无相关性!
1 2 3 4 5 6 7 #pragma omp parallel { #pragma omp for for (int i = 0 ; i < N; i++) { A[i] = i * 2 ; } }
parallel for 等价于 parallel + for
1 2 3 4 #pragma omp parallel for for (int i = 0 ; i < N; i++) { A[i] = i * 2 ; }
sections 把不同的代码段(section)分配给不同的线程去执行,每个 section 只会执行一次。
相当于 任务并行(task parallelism) ,而不是 数据并行(data parallelism) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> #include <omp.h> int main () { #pragma omp parallel { #pragma omp sections { #pragma omp section { printf ("Thread %d doing task A\n" , omp_get_thread_num()); } #pragma omp section { printf ("Thread %d doing task B\n" , omp_get_thread_num()); } #pragma omp section { printf ("Thread %d doing task C\n" , omp_get_thread_num()); } } } return 0 ; }
parallel sections single 在一个并行区域里,只允许一个线程执行这段代码 ,而其他线程 跳过它,继续往下执行 。
1 2 3 4 5 6 7 #pragma omp parallel { #pragma omp single { printf ("Only one thread executes this!\n" ); } }
barrier 让所有线程在这里集合(同步点),只有当 所有线程都到达这个位置 时,才会一起继续往下执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> #include <omp.h> int main () { #pragma omp parallel { int tid = omp_get_thread_num(); printf ("Thread %d: part A done\n" , tid); #pragma omp barrier printf ("Thread %d: part B done\n" , tid); } return 0 ; } Thread 0 : part A done Thread 2 : part A done Thread 1 : part A done Thread 3 : part A done Thread 0 : part B done Thread 1 : part B done Thread 2 : part B done Thread 3 : part B done
part A 部分的输出是乱序的(线程独立执行)。
但是所有线程必须 等到 barrier ,再一起进入 part B 。
所以 part B 的内容永远不会早于任何一个 part A 。
nowait 取消隐式的 barrier ,也就是不让线程在循环或指令后等待其他线程完成,而是直接继续执行后续代码。
1 2 3 4 5 #pragma omp for for (int i = 0 ; i < N; i++) { }
1 2 3 4 5 #pragma omp for nowait for (int i = 0 ; i < N; i++) { }
当循环后面的代码不依赖 于当前循环的完成结果时,可以加 nowait 提高并行效率。
如果后续代码依赖 当前循环的结果,不能用 nowait ,否则会出现数据竞争或错误。
if 条件性地决定是否在并行区域创建线程 。也就是说,可以根据一个布尔条件来选择执行并行版本还是串行版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> #include <omp.h> int main () { int N = 5 ; #pragma omp parallel if (N > 10) { } return 0 ; }
atomic 保证某个内存操作是原子的 (不可分割的),防止多个线程同时修改同一个变量时产生 竞争条件 (race condition) 。
但只能管一行语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> #include <omp.h> int main () { int sum = 0 ; #pragma omp parallel for for (int i = 0 ; i < 1000 ; i++) { #pragma omp atomic sum += 1 ; } printf ("sum = %d\n" , sum); return 0 ; }
critical 作用等同于atomic,用于一段代码,保证一段代码 同一时刻只允许一个线程进入
1 2 3 4 5 6 7 8 9 int sum = 0 ;#pragma omp parallel for for (int i = 0 ; i < 10 ; i++) { #pragma omp critical { sum += i; } }
single VS critical :
single = “只做一次,随便哪个线程做就行”
critical = “大家都要做,但得一个一个排队做,不能一起做”
master 指定一段代码由主线程(thread 0)执行,其余线程跳过,不等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> #include <omp.h> int main () { #pragma omp parallel { int tid = omp_get_thread_num(); #pragma omp master { printf ("This is printed only by the master thread (tid = %d)\n" , tid); } printf ("Thread %d reached here.\n" , tid); } return 0 ; }
只有主线程会执行里面的printf, 其他线程都执行外面的printf
orderd 用来保证在并行循环中某些操作按循环迭代的顺序执行 ,即使循环是并行执行的。
当你用 #pragma omp for 并行循环时,每个迭代可能被不同线程同时执行。
如果某些操作必须严格按照迭代顺序执行 (比如输出、累加到共享数组),就可以使用 ordered。
必须在 for 循环中加上 ordered 子句,然后在循环体内用 #pragma omp ordered 标记需要按顺序执行的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <stdio.h> #include <omp.h> int main () { #pragma omp parallel for ordered for (int i = 0 ; i < 8 ; i++) { int val = i * i; #pragma omp ordered printf ("i=%d, val=%d\n" , i, val); } return 0 ; }
子句 shared 指定变量在多个线程间共享。
1 2 3 4 5 int sum = 0 ;#pragma omp parallel for shared(sum) for (int i = 0 ; i < 10 ; i++) { sum += i; }
private / firstprivate / lastprivate
private(var) :每个线程有独立的 var 副本,原来的共享变量不会被线程修改。
firstprivate(var) :每个线程有独立副本,并初始化为原来的值。
lastprivate(var) :循环结束后,最后一次迭代(代码理论上的最后一次)的线程的 var 值写回原来的共享变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 int x = 10 ;#pragma omp parallel for private(x) for (int i = 0 ; i < 4 ; i++) { x = i; } #pragma omp parallel for firstprivate(x) for (int i = 0 ; i < 4 ; i++) { printf ("%d\n" , x); }
1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { int x = 0 ; #pragma omp parallel for lastprivate(x) for (int i = 0 ; i < 10 ; i++) { x = i; printf ("Thread %d: x = %d\n" , omp_get_thread_num(), x); } printf ("After loop, x = %d\n" , x); return 0 ; }
threadprivate 用于声明每个线程都有自己独立的全局或静态变量副本 ,每个线程访问的是自己独立的副本,互不干扰。
默认情况下,全局变量是所有线程共享的 。
如果你希望每个线程有自己的独立版本,可以用 threadprivate。
和局部变量不同,局部变量天然就是线程私有的;threadprivate 针对的是全局或 static 变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <omp.h> #include <stdio.h> int counter = 0 ; #pragma omp threadprivate(counter) int main () { #pragma omp parallel num_threads(4) { counter = omp_get_thread_num(); printf ("Thread %d: counter = %d\n" , omp_get_thread_num(), counter); } return 0 ; } Thread 1 : counter = 1 Thread 2 : counter = 2 Thread 0 : counter = 0 Thread 3 : counter = 3
default
设置并行区域中变量的默认属性:
default(none):所有变量必须显式声明 shared 或 private,安全
default(shared):未声明的变量默认共享
default(private):未声明的变量默认私有
1 2 3 4 5 6 int x, y;#pragma omp parallel default(none) shared(x) private(y) { y = omp_get_thread_num(); x = y; }
reduction
对共享变量进行归约操作(累加、乘积、最大值、最小值等),保证结果正确。
1 2 3 4 5 6 7 int sum = 0 ;#pragma omp parallel for reduction(+:sum) for (int i = 0 ; i < 100 ; i++) { sum += i; } printf ("sum = %d\n" ,sum);
copyin 用来给 线程私有的全局或静态变量(用 threadprivate 声明) 初始化值。它把 主线程(thread 0)的值复制到所有线程的私有副本 。
换句话说,threadprivate 声明的变量每个线程都有自己的副本,而 copyin 可以用来统一初始化这些副本 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <omp.h> int counter = 0 ;#pragma omp threadprivate(counter) int main () { counter = 42 ; #pragma omp parallel copyin(counter) { printf ("Thread %d: counter = %d\n" , omp_get_thread_num(), counter); counter += omp_get_thread_num(); } printf ("Main thread counter = %d\n" , counter); return 0 ; } Thread 0 : counter = 42 Thread 1 : counter = 42 Thread 2 : counter = 42 Thread 3 : counter = 42 Main thread counter = 42
counter 是 threadprivate,每个线程有独立副本
copyin(counter) 把主线程的值(42)复制给每个线程的副本
每个线程修改自己的 counter 不会影响其他线程
主线程的 counter 保持原值
copyprivate
copyprivate 是 OpenMP 的 single 子句 的一部分。
作用:在 parallel 区域中,single 区块由一个线程执行,但这个线程的变量值可以复制给其他线程 。
通常配合 #pragma omp single 使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <omp.h> int main () { int x; #pragma omp parallel { #pragma omp single copyprivate(x) { x = 42 ; printf ("Single thread sets x = %d\n" , x); } printf ("Thread %d sees x = %d\n" , omp_get_thread_num(), x); } return 0 ; }
collapse collapse(n) 用来 把 n 层嵌套循环合并为一个大的迭代空间 ,再分配给线程执行。
1 2 3 4 5 6 7 8 #pragma omp parallel for for (int i = 0 ; i < 4 ; i++) { for (int j = 0 ; j < 4 ; j++) { printf ("Thread %d: i=%d, j=%d\n" , omp_get_thread_num(), i, j); } }
1 2 3 4 5 6 7 8 #pragma omp parallel for collapse(2) for (int i = 0 ; i < 4 ; i++) { for (int j = 0 ; j < 4 ; j++) { printf ("Thread %d: i=%d, j=%d\n" , omp_get_thread_num(), i, j); } }
collapse = 把多层循环“摊平”成一层迭代空间
适合嵌套循环层次少,迭代次数不够多 的情况
可以与 schedule 结合,提高负载均衡
任务调度 schedule
schedule 是 OpenMP 的 for 或 parallel for 子句 的一部分,用来控制 循环迭代分配给线程的策略 。
主要作用:决定线程如何分配迭代任务,以及每次分配多少个迭代 。
type:static, dynamic, guided, runtime(真正意义上不算)
chunck_size:块大小
1 schedule(type[,chunk_size])
类型
说明
示例
static
循环迭代 均匀分块 分配给线程。默认 chunk_size = N / num_threads
#pragma omp for schedule(static)
static, chunk_size
每个线程一次分配 chunk_size 个迭代,按顺序循环分配
#pragma omp for schedule(static, 2)
dynamic
循环迭代 动态分配 ,线程完成后再拿新的迭代块。适合负载不均的循环
#pragma omp for schedule(dynamic, 3)
guided
每次分配迭代块大小 逐渐减小 ,前期大块,后期小块。如果不指定chunk size,最小可以到1
#pragma omp for schedule(guided, 2)
auto
让编译器/运行时自动选择调度策略
#pragma omp for schedule(auto)
runtime
运行时根据环境变量 OMP_SCHEDULE 决定
#pragma omp for schedule(runtime)
static :负载均匀,开销小
dynamic :负载不均,线程可以“抢活”,开销略大
guided :负载递减型动态分配
auto / runtime :交给编译器或环境变量控制
task
Task 是 OpenMP 的一个并行单位 ,表示一段可以延迟执行的代码块。
可以想象成一个 “工作单元”,由线程池中的线程去执行。
与 parallel for 不同 ,它不一定是循环的迭代,也可以是任意代码块。
Task 可以有 依赖关系(depend) ,保证任务之间的执行顺序。
特点:
延迟执行 :创建 task 时,OpenMP 不会立即执行它,而是放入任务队列等待线程执行。
灵活性高 :可以表示任意代码,不只是循环。
线程复用 :多个 task 可以被线程池中的空闲线程执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> #include <omp.h> int main () { #pragma omp parallel { #pragma omp single { #pragma omp task printf ("Task 1 executed by thread %d\n" , omp_get_thread_num()); #pragma omp task printf ("Task 2 executed by thread %d\n" , omp_get_thread_num()); } } return 0 ; }
single 保证任务只被创建一次。
任务可以被任意空闲线程执行。
depend 在计算前缀和、Fibonacci 或矩阵块操作中,任务可能需要依赖其他任务完成的结果。
1 2 3 4 #pragma omp task depend(in: a) depend(out: b) { }
in: 表示依赖输入,任务必须等待这些数据准备好。
out: 表示任务会写这个数据。
inout: 表示任务既读又写。
1 2 3 4 5 6 7 8 9 10 #pragma omp parallel #pragma omp single for (int x = 0 ; x < N; x++) { for (int y = 0 ; y < N; y++) { #pragma omp task depend(in: B[x-1][y], B[x][y-1], B[x-1][y-1]) depend(out: B[x][y]) B[x][y] = B[x-1 ][y] + B[x][y-1 ] - B[x-1 ][y-1 ] + A[x][y]; } }
taskwait 用于 等待当前线程生成的所有子任务完成 ,然后再继续往下执行。
它只等待 同一线程创建的任务 。
不会等待其他线程创建的任务(除非它们是依赖任务)。
放在并行区域或单线程区域中。
后面不跟任何语句,只是一个同步点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> #include <omp.h> int main () { #pragma omp parallel #pragma omp single { #pragma omp task printf ("Task 1\n" ); #pragma omp task printf ("Task 2\n" ); #pragma omp taskwait printf ("All tasks finished!\n" ); } return 0 ; } Task 1 Task 2 All tasks finished!
环境变量与 API 对照表
功能类别
环境变量
对应 API(代码里设置)
说明
线程数
OMP_NUM_THREADS
omp_set_num_threads(int n) omp_get_num_threads()
控制并行区域的线程数
动态线程调整
OMP_DYNAMIC (TRUE/FALSE)
omp_set_dynamic(int flag) omp_get_dynamic()
是否允许运行时调整线程数
线程总上限
OMP_THREAD_LIMIT
omp_get_thread_limit()
限制整个程序最多线程数
嵌套并行
OMP_NESTED(旧标准) OMP_MAX_ACTIVE_LEVELS
omp_set_max_active_levels(int levels) omp_get_max_active_levels()
设置并行嵌套层数
循环调度
OMP_SCHEDULE
omp_set_schedule(omp_sched_t kind, int chunk) omp_get_schedule(&kind, &chunk)
控制 for 循环调度策略
绑定和亲和性
OMP_PROC_BIND
omp_get_proc_bind()
控制线程是否绑定到 CPU
OMP_PLACES
无(环境变量指定)
线程可绑定的位置(cores/threads/sockets)
任务
OMP_MAX_TASK_PRIORITY
无
控制任务最大优先级
调试 & 性能
OMP_DISPLAY_ENV (TRUE/FALSE)
无
是否显示 OpenMP 环境设置
OMP_WAIT_POLICY (ACTIVE/PASSIVE)
无
控制空闲线程等待策略
OMP_CANCELLATION (TRUE/FALSE)
omp_get_cancellation()
是否允许取消任务/循环
1 2 3 4 export OMP_NUM_THREADS=8 export OMP_SCHEDULE="dynamic,2" export OMP_PROC_BIND=spread export OMP_DISPLAY_ENV=TRUE
OpenMP 常用函数 1.线程管理
函数
说明
omp_set_num_threads(int n)
设置并行区域中的线程数(仅对后续并行区有效)
omp_get_num_threads()
获取当前并行区域的线程数
omp_get_thread_num()
获取当前线程编号(0 ~ n-1)
omp_get_max_threads()
返回可能使用的最大线程数
omp_set_dynamic(int flag)
开启/关闭动态调整线程数
omp_get_dynamic()
判断是否允许动态调整线程数
omp_get_thread_limit()
获取程序允许的最大线程数
2.并行控制
函数
说明
omp_in_parallel()
判断当前是否在并行区域内
omp_set_nested(int flag) (旧)
设置是否支持嵌套并行(OMP 4.0 前)
omp_set_max_active_levels(int levels)
设置最大并行嵌套层数
omp_get_max_active_levels()
获取最大嵌套层数
omp_get_level()
获取当前并行嵌套层数
omp_get_ancestor_thread_num(int level)
获取某层的祖先线程编号
3.调度与任务
函数
说明
omp_set_schedule(omp_sched_t kind, int chunk)
设置循环调度策略(static, dynamic, guided, auto)
omp_get_schedule(omp_sched_t *kind, int *chunk)
获取当前调度策略和 chunk 大小
omp_get_wtime()
获取墙钟时间(高精度计时)
omp_get_wtick()
获取 omp_get_wtime() 的精度
omp_get_num_procs()
获取可用的处理器数
omp_get_proc_bind()
获取线程绑定策略
4.任务与取消
函数
说明
omp_get_cancellation()
是否支持取消任务
omp_get_default_device()
获取默认设备(比如 GPU 设备 ID)
omp_set_default_device(int device_num)
设置默认设备
5.锁(同步机制)
函数
说明
omp_init_lock(omp_lock_t *lock)
初始化锁
omp_destroy_lock(omp_lock_t *lock)
销毁锁
omp_set_lock(omp_lock_t *lock)
获取锁(阻塞等待)
omp_unset_lock(omp_lock_t *lock)
释放锁
omp_test_lock(omp_lock_t *lock)
尝试获取锁(不阻塞,立即返回)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> #include <omp.h> int main () { printf ("最大线程数: %d\n" , omp_get_max_threads()); printf ("处理器数: %d\n" , omp_get_num_procs()); double t1 = omp_get_wtime(); #pragma omp parallel { int id = omp_get_thread_num(); printf ("线程 %d / %d 正在运行\n" , id, omp_get_num_threads()); } double t2 = omp_get_wtime(); printf ("耗时: %f 秒\n" , t2 - t1); return 0 ; }
OpenMP 编程常见坑点总结 1.循环迭代变量没声明为 private 写了平行区域 + for 循环 ,但是忘了加 private(i) → 会发生数据竞争。
1 2 3 4 5 6 #pragma omp parallel { for (int i = 0 ; i < 10 ; i++) { printf ("Thread %d: i=%d\n" , omp_get_thread_num(), i); } }
这里 i 是 共享的(shared) ,所有线程同时用 同一个变量 i 。
当多个线程同时修改 i,就会发生 数据竞争 (race condition) 。
English: The loop variable i is shared across threads, so multiple threads update it at the same time, causing a race condition.
解决办法,加上private(i) 或者使用parallel for(会自动把 i 设置成private)
1 2 3 4 5 6 7 8 9 10 11 #pragma omp parallel private(i) { for (int i = 0 ; i < 10 ; i++) { printf ("Thread %d: i=%d\n" , omp_get_thread_num(), i); } } #pragma omp parallel for for (int i = 0 ; i < 10 ; i++) { printf ("Thread %d: i=%d\n" , omp_get_thread_num(), i); }
2.数据竞争(race condition)
多个线程同时写共享变量 → 结果不确定。
常见于计数、累加。
English: Shared variables modified by multiple threads without synchronization will lead to unpredictable results.
1 2 3 4 5 6 7 8 int sum = 0 ;for (int i = 0 ; i < 1000 ; i++) { sum ++; } for (int i = 0 ; i < 1000 ; i++) sum ++;
3.reduction 忘记 reduce
很多人写并行求和、最值时直接 shared → 结果错误。
English: Without reduction, all threads write to the same shared variable at the same time, causing wrong results.
1 2 3 4 5 6 7 int sum = 0 ;for (int i = 0 ; i < N; i++) sum += A[i]; for (int i = 0 ; i < N; i++) sum += A[i];
reduction 忘记初始化
reduction 会在每个线程里生成 sum 的副本,但如果 sum 没初始化,结果是随机的。
English: If the reduction variable is not initialized, each thread’s private copy starts with an undefined value, leading to incorrect results.
1 2 3 int sum;#pragma omp parallel for reduction(+:sum) for (int i=0 ; i<10 ; i++) sum += i;
1 2 3 4 int sum = 0 ;#pragma omp parallel for reduction(+:sum) for (int i=0 ; i<10 ; i++) sum += i;
4.barrier / nowait 使用不当
默认 for 和 sections 结束时会有隐式 barrier。
如果用 nowait,要小心后续代码是否需要同步。
English: Using nowait skips synchronization, so some threads may read data before others finish writing.
1 2 3 4 5 6 7 8 #pragma omp parallel { #pragma omp for nowait for (int i=0 ;i<10 ;i++) A[i]=i; printf ("Thread %d: A[5] = %d\n" , omp_get_thread_num(), A[5 ]); }
所以有些线程可能还在写 A[i],但别的线程已经跑到 printf 了。
结果就可能访问到 未初始化或部分更新的 A ,产生 竞态条件 (race condition) 。
去掉 nowait (默认会有 barrier,同步所有线程):
1 2 3 4 5 6 7 8 #pragma omp parallel { #pragma omp for for (int i=0 ;i<10 ;i++) A[i]=i; printf ("Thread %d: A[5] = %d\n" , omp_get_thread_num(), A[5 ]); }
保留 nowait,但手动加 barrier :
1 2 3 4 5 6 7 8 9 #pragma omp parallel { #pragma omp for nowait for (int i=0 ;i<10 ;i++) A[i]=i; #pragma omp barrier printf ("Thread %d: A[5] = %d\n" , omp_get_thread_num(), A[5 ]); }
5.task 忘记加 taskwait
如果 task 有依赖关系,必须手动同步。
English: Without taskwait, the program may continue before all tasks have finished, leading to incomplete results.
1 2 3 4 5 6 7 8 9 10 11 12 #pragma omp parallel { #pragma omp single { #pragma omp task do_A (); #pragma omp task do_B (); #pragma omp taskwait } }
6.循环嵌套没有 collapse
多重循环并行时,只 parallel 外层 → 内层仍是串行。
English: Without collapse, only the outer loop is parallelized, so the workload may not be fully distributed.
1 2 3 4 5 6 7 8 9 #pragma omp parallel forfor (int i=0 ;i<N;i++) for (int j=0 ;j<N;j++) work (i,j); #pragma omp parallel for collapse(2 )for (int i=0 ;i<N;i++) for (int j=0 ;j<N;j++) work (i,j);
7. schedule 不当
schedule(static) 适合工作量均匀的循环;
schedule(dynamic, chunk) 适合负载不均的循环;
乱用可能导致线程负载不平衡。
English: Using the wrong scheduling strategy can cause load imbalance, where some threads finish early and others are overloaded.
8.乱用 critical / atomic
critical 会严重拖慢性能,如果只是单行更新 → 用 atomic。
English: critical forces threads to serialize access, which is slower; if only a single statement is updated, atomic is better.
1 2 3 4 5 6 7 8 #pragma omp parallel for for (int i=0 ;i<N;i++) { #pragma omp critical sum += A[i]; } #pragma omp atomic sum += A[i];
9.输出打印错乱
printf 是共享 I/O,多个线程同时写会交错。
English: Standard output is shared; multiple threads writing at the same time will interleave their messages.
如果要有序输出,用 ordered 或加锁。
1 2 3 4 5 #pragma omp parallel for ordered for (int i=0 ;i<8 ;i++) { #pragma omp ordered printf ("i=%d\n" , i); }
10.忘记设置默认共享/私有变量(default 子句)
1 2 3 4 5 6 int x = 10 , y = 20 ;#pragma omp parallel { x += omp_get_thread_num(); printf ("Thread %d: x=%d\n" , omp_get_thread_num(), x); }
1 #pragma omp parallel default(none) private(x) shared(y)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int x = 10 , y = 20 ;#pragma omp parallel private(x) { x = 10 ; x += omp_get_thread_num(); printf ("Thread %d: x=%d\n" , omp_get_thread_num(), x); } int x = 10 ;#pragma omp parallel reduction(+:x) { x += omp_get_thread_num(); } printf ("Final x = %d\n" , x);
11. PPT
#pragma omp parallel for num_threads(P)
for (int i = 0; i < N; i++) {
#pragma omp task
h(i);
}
1 2 3 4 5 6 7 - 在 OpenMP 中,如果没有明确的同步机制(如 `taskwait` 或 `barrier`),父任务(在这里是循环迭代)可以在子任务(h(i))完成前就结束。 - 当所有循环迭代完成后,parallel 区域会立即结束。此时,可能有许多任务(h(i))尚未执行完成。 - Explicit synchronization mechanisms (such as taskwait or barrier) cause the parallel region to end immediately after all loop iterations are completed. At this point , many tasks (h(i)) may not have finished executing. **在 single 区域中创建任务并等待**:
#pragma omp parallel num_threads(P)
{
#pragma omp single
{
for (int i = 0; i < N; i++) {
#pragma omp task
h(i);
}
#pragma omp taskwait // 等待所有任务完成
}
}