OpenMP同步&互斥与任务调度

多线程编程需要考虑的资源竞争问题而引入互斥与同步
为提高并行率而需要任务调度分配

互斥

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指定的线程块只会被执行一次