OpenMP编程(6)—数据作用域(private、firstprivate、lastprivate、shared、default、reduction、copyin、threadprivate)

    技术2023-05-29  74

    数据作用域说明:

    数据作用域定义了程序串行部分中的数据变量中的哪些以及如何传输到程序的并行部分,定义了哪些变量对并行部分中的所有线程可见,以及哪些变量将被私有地分配给所有线程。数据作用域子句可与多种指令(如parallelL、for、sections等)一起使用,以控制封闭区域变量的作用域OpenMP基于共享内存编程模型,所以大多数变量在默认情况下是共享的
    1. parivate
    parivate子句声明其列表中的变量对每个线程都是私有的。从申明的并行区域开始,为每个线程声明一个相同类型的新对象,对原始对象的所有引用都将替换为对新对象的引用对于每个线程,声明为私有的变量未初始化 示例代码如下 int i = 0; float a = 512.3; #pragma omp parallel private(i,a) { i = i + 1; printf("thread %d i = %d a= %f\n", omp_get_thread_num(), i, a); } printf("out of parallel i = %d", i); }

    运行结果如下,每个线程都有一个独立的i变量,互不影响,同时也不影响原始的i。

    2. firstprivate
    firstprivate子句指定的变量不仅是private作用范围,同时在进入并行区域构造前根据其原始对象的值初始化 示例代码如下 int i = 1; float a = 512.3; #pragma omp parallel firstprivate(i) private(a) { i = i + 1; printf("thread %d i = %d a= %f\n", omp_get_thread_num(), i, a); } printf("out of parallel i = %d", i);

    运行结果如下,每个线程都有一个独立的i变量,且进入并行区域时每个线程的i都被初始化原始值1。

    3. lastprivate
    lastprivate子句指定的变量不仅是private作用范围,同时会将最后一次迭代或最后一个section执行的值复制回原来的变量 示例代码如下 int i = 0; float a = 512.3; #pragma omp parallel { #pragma omp sections lastprivate(i) private(a) { #pragma omp section { i = i + omp_get_thread_num(); printf("thread %d i = %d a= %f\n", omp_get_thread_num(), i, a); } #pragma omp section { i = i + omp_get_thread_num(); printf("thread %d i = %d a= %f\n", omp_get_thread_num(), i, a); } } } printf("out of parallel i = %d", i);

    运行结果如下,每个线程都有一个独立的i变量,且退出并行区域时原始的i变量被设置成其中一个section线程的相应变量值。

    4. shared
    shared子句声明的变量将在所有线程之间共享,所有线程都可以对同一个内存位置进行读写操作程序员需确保多个线程正确地访问共享变量(通过critical等指令) 示例代码如下 int i = 0; float a = 512.3; #pragma omp parallel shared(i,a) { i = i + 1; printf("thread %d i = %d a= %f\n", omp_get_thread_num(), i, a); } printf("out of parallel i = %d", i); }

    运行结果如下,每个线程公用同一个的i变量,且与原始值也是同一变量。

    5. default
    default子句允许用户为任何并行区域范围内的所有变量指定默认的private、shared或none作用范围。同时可使用private、shared、firstprivate、lastprivate和reduction子句免除特定变量的缺省值
    6. reduction
    reduction子句对其列表中出现的变量进行规约。首先在并行区域为每个线程创建该变量的私有副本。在并行区域结束时,根据指定的运算将所以线程的私有副本执行规约操作后,赋值给原来的变量中。reduction列表中的变量必须命名为标量变量,不能是数组或结构类型变量,还必须在并行区域中声明为共享 下面的代码示例是计算向量的内积 int i, n, chunk; float a[100], b[100], result; 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);

    运行结果为656700,是向量a和b的内积值。首先在并行区域开始处,分别为每个线程创建一份副本,然后每个线程分别计算一部分元素的乘积和,结果在自己的副本result中。然后在并行区域结束处,每个result副本根据原先声明的reduce操作+,将每个result副本相加后赋值给原始的result变量。

    7. threadprivate
    threadprivate指令用于将全局变量的副本与线程绑定,即使跨越多个并行区域这种关系也不会改变。该指令必须出现在所列变量声明之后。然后每个线程都会获得自己的变量副本,因此一个线程写入的数据对其他线程不可见。在第一次进入并行区域时,应假定threadprivate变量的数据未定义,除非parallel指令中指定了copyin子句要使用threadprivate,必须关闭动态线程机制,同时不同并行区域中的线程数保持不变。若设置为动态线程则结果未定义 -threadprivate与private不同之处在于,threadprivate变量副本与线程的关系可以跨越多个并行区域,而private变量的副本与线程的关系仅在本声明区域起作用 代码示例如下 int a, b, i, tid; float x; #pragma omp threadprivate(a, x) void TestThreadPrivate() { //关闭动态线程 omp_set_dynamic(0); printf("1st Parallel Region:\n"); #pragma omp parallel private(b,tid) { tid = omp_get_thread_num(); a = tid; b = tid; x = 1.1 * tid +1.0; printf("Thread %d:a=%d, b=%d, x= %f\n",tid,a,b,x); } printf("2nd Parallel Region:\n"); #pragma omp parallel private(tid) { tid = omp_get_thread_num(); printf("Thread %d:a=%d, b=%d, x= %f\n",tid,a,b,x); } }

    运行结果如下,可见threadprivate变量在多个并行区域中,其值与线程的对应关系保持不变,其实是变量副本与线程关系保持不变。

    8. copyin
    copyin子句用于threadprivate变量,可以为所有线程的threadprivate变量分配相同的值。设置为copyin的threadprivate变量,进入并行区域时,会用主线程变量值为每个线程的该变量副本初始化。
    Processed: 0.057, SQL: 12