背景(Open Multi-Processing)
OMP是一套跨平台共享内存多线程并发编程API,目的是提高计算速度
API接口
### 头文件
#include <omp.h>
### 常用API接口
#pragma omp parallel num_threads(4) default(none) shared(data,std::cout)
启动四个线程,default(none)要求所有变量都要显示指定
data变量所有线程均可访问
std::cout实际上是一个全局变量,所以需要显示指定,如果在其他情况下,需要用#pragma omp critical来加锁,该变量只能由一个线程访问
#pragma omp single
表示只需要一个线程执行以下语句
#pragma omp barrier
表示所有线程都执行完该命令后,才继续执行
#pragma omp sections
表示多个线程可以同时执行,但是需要等待所有线程都执行完才继续执行
#pragma omp section
表示以下不同代码块会被分配不同的线程
#pragma omp critical(A)
表示A锁,不指定锁命默认critical使用同一把锁
omp_get_wtime()
返回lf时间
omp_get_thread_num()
获得线程id
omp_set_nested(1)
启动多层嵌套,内外多线程,嵌套并行
pragma omp parallel for
是 OpenMP 中用于 并行循环 的指令。它可以让多个线程自动分配循环迭代任务,加速代码执行。
错误示范:
int parallel_worng_arraysum(){
int sum = 0;
int a[10] = {1,2,3,4,5,6,7,8,9,10};
#pragma omp parallel for
for (int i=0;i<10;i++)
sum = sum + a[i];
return sum;
}
`sum` 的更新在并行环境下导致数据竞争,因为多个线程可能会同时更新 `sum`。故该方法并不安全
修改方法
#pragma omp parallel for reduction(+:sum)
使用该方法课加法规约优化
#pragma omp for nowait
该 `for` 循环结束后,不需要强制执行线程的隐式同步。意味着某一线程完成任务后可执行其他任务
#pragma omp ordered
#pragma omp parallel for schedule(dynamic)
动态调度(dynamic)
任务分配方式:每个线程先分配一部分循环迭代任务,执行完后再领取新任务,直到所有任务完成。
适用场景:
每次循环的计算量不均匀(比如有些任务很重,有些任务很轻)。
适用于 不确定的计算任务,比如数据依赖或分支跳转的计算。
// sections 后面存在隐藏同步点
#pragma omp sections nowait
#pragma omp sections后的代码会等待所有section完成之后才执行,如果加入nowait,则每个线程完成section部分后会直接运行second之后的代码
示例:
#include <omp.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
#pragma omp parallel num_threads(4) default(none)
{
// sections 后面存在隐藏同步点
#pragma omp sections nowait
{
#pragma omp section
{
int s = omp_get_thread_num() + 1;
sleep(s);
printf("tid = %d sleep %d seconds\n", s, s);
}
#pragma omp section
{
int s = omp_get_thread_num() + 1;
sleep(s);
printf("tid = %d sleep %d seconds\n", s, s);
}
#pragma omp section
{
int s = omp_get_thread_num() + 1;
sleep(s);
printf("tid = %d sleep %d seconds\n", s, s);
}
#pragma omp section
{
int s = omp_get_thread_num() + 1;
sleep(s);
printf("tid = %d sleep %d seconds\n", s, s);
}
}
printf("tid = %d finish sections\n", omp_get_thread_num());
}
return 0;
}
运行结果为:
如果不适用nowait,所有的线程会等待直到所有线程完成section内的命令,运行结果就为:
对比发现没有nowait的时候所有section运行结束后才会运行section后的代码
代码仓库: