多线程编程需要考虑的资源竞争问题而引入互斥与同步
为提高并行率而需要任务调度分配
互斥
critical指令
#include "omp.h"
#include "iostream"
#include <stdlib.h>
using namespace std;
int main(){
int sum=0;
#pragma omp parallel for
for(int i=0;i<10;++i){
#pragma omp critical
{
sum +=i;
}
}
cout<<"sum "<<sum<<endl;
}
critical可以将指定的代码块加锁
使得任何时候只有一个线程可访问这块区域
atomic指令(原子操作)
将上面代码中的
#pragma omp critical
{
sum +=i;
}
替换为
#pragma omp atomic
sum +=i;
也可以实现加锁
与之不同的是atomic是对单个指令加锁
同步
barrier指令
#include "omp.h"
#include "iostream"
#include <stdio.h>
using namespace std;
int main(){
int sum=0;
#pragma omp parallel
{
for(int i=0;i<10;++i){
sum +=i;
}
#pragma omp barrier
printf("sum: %d\n",sum);
}
}
程序输出如下
sum: 105
sum: 105
sum: 105
sum: 105
如果不加 #pragma omp parallel
输出为
sum: 73
sum: 45
sum: 90
sum: 45
barrier指令使得各线程同步访问sum变量使得输出一致
OpenMP采用的是fork-join执行模式
故在每个并行的线程块结束后都会隐式同步(等效barrier)
可以使用nowait子句取消
如(都没有parallel)
#pragma omp for nowait
#pragma omp sections nowait
ordered子句
#include "omp.h"
#include "iostream"
#include <stdio.h>
using namespace std;
int main(){
#pragma omp parallel for ordered
for(int i=0;i<4;++i){
#pragma omp ordered
printf("id: %d\n",omp_get_thread_num());
}
}
运行结果如下
id: 0
id: 1
id: 2
id: 3
在parallel for后加上ordered子句后
就可以在代码块中使用 #pragma omp ordered 同步执行
master子句
int main(){
#pragma omp parallel
{
#pragma omp master
{
printf("id: %d\n",omp_get_thread_num());
}
}
}
程序输出如下
id: 0
这个子句保证了这块代码只由主线程执行
任务分配
schedule子句
static 静态调度
int main(){
#pragma omp parallel for schedule(static,3)
for(int i=0;i<8;++i)
{
printf("id: %d\n",omp_get_thread_num());
}
}
输出如下
id: 0
id: 0
id: 0
id: 2
id: 2
id: 1
id: 1
id: 1
dynamic 动态调度
int main(){
#pragma omp parallel for schedule(dynamic,2)
for(int i=0;i<12;++i)
{
printf("result: %d\tid:%d\n",
i*i*i*i*i*i,
omp_get_thread_num());
}
}
执行结果如下
result: 64 id:0
result: 729 id:0
result: 262144 id:0
result: 531441 id:0
result: 1000000 id:0
result: 1771561 id:0
result: 4096 id:2
result: 15625 id:2
result: 46656 id:3
result: 117649 id:3
result: 0 id:1
result: 1 id:1
(dynamic,2) 每次分配给线程2个任务
谁先完成谁继续接受任务
使得能达到核心的高占用率
guided调度
一开始给每个线程分配多于size个任务
然后分配的任务数指数级下降
直到分配任务数下降到size后不变
runtime调度
根据环境变量OMP_SCHEDULE选择调度方式
最终还是落实到上面三个的其中一个
single指令
int main(){
#pragma omp parallel
{
#pragma omp single
{
printf("hello world\n");
}
printf("nice to meet you\n");
}
}
程序执行如下
hello world
nice to meet you
nice to meet you
nice to meet you
nice to meet you
被single指定的线程块只会被执行一次