OpenMP模式下多线程文件操作(二)

数据的共享和私有化

1. 引言

    在并行区域内,若多个线程共同访问同一个存储单元,并且至少会有一个线程更新数据单元中的内容时,会发生数据竞争。本节的数据共享和私有化对数据竞争做一个初步探讨,后续会涉及同步、互斥的内容。

2. 并行区域内的变量的共享和私有

    除了以下三种情况外,并行区域中的所有变量都是共享的:

    > 并行区域中定义的变量

    > 多个线程用来完成循环的循环变量

    > private、firstprivate、lastprivate、reduction修饰的变量

    例如,

   

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <iostream>   
  2. #include <omp.h>   
  3.   
  4. int main()  
  5. {  
  6.    int share_a = 0; // 共享变量   
  7.     int share_to_private_b = 1;  
  8.   
  9.    #pragma omp parallel   
  10.    {  
  11.       int private_c = 2;  
  12.    //通过private修饰后在并行区域内变为私有变量   
  13.    #pragma omp for private(share_to_private_b)   
  14.       for(int i = 0; i < 10; ++i)  
  15.       {//该循环变量是私有的,若为两个线程,则一个执行0<=i<5,另一个执行5<=i<10   
  16.          std::cout << i << std::endl;  
  17.       }  
  18.    }  
  19.   
  20.    return 0;  
  21. }  

3. 共享与私有变量声明的方法

    private(val1, val2, ...)          并行区域中变量val是私有的,即每个线程拥有该变量的一个copy

    firstprivate(val1, val2, ...)    与private不同,每个线程在开始的时候都会对该变量进行一次初始化

    lastprivate(val1, val2, ...)    与private不同,并发执行的最后一次循环的私有变量将会copy到val

    shared(val1, val2, ...)          声明val是共享的

4. private示例

    如果使用private,无论该变量在并行区域外是否初始化,在进入并行区域后,该变量均不会初始化。

    在VS2010下,会因为private所导致的私有变量未初始化而出现错误。例如:

   

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <iostream>   
  2. #include <omp.h>   
  3.   
  4. int main()  
  5. {  
  6.    //通过private修饰该变量之后在并行区域内变为私有变量,进入并行   
  7.     //区域后每个线程拥有该变量的拷贝,并且都不会初始化   
  8.    int shared_to_private = 1;  
  9.   
  10. #pragma omp parallel for private(shared_to_private)   
  11.    for(int i = 0; i < 10; ++i)  
  12.    {  
  13.       std::cout << shared_to_private << std::endl;  
  14.    }  
  15.   
  16.    return 0;  
  17. }  
  18. F5调试由于变量shared_to_rivate未初始化而崩掉。  

5. firstprivate示例

   

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <iostream>   
  2. #include <omp.h>   
  3.   
  4. int main()  
  5. {  
  6.    //通过firstprivate修饰该变量之后在并行区域内变为私有变量,   
  7.     //进入并行区域后每个线程拥有该变量的拷贝,并且会初始化   
  8.    int share_to_first_private = 1;  
  9.   
  10. #pragma omp parallel for firstprivate(share_to_first_private)   
  11.    for(int i = 0; i < 10; ++i)  
  12.    {  
  13.       std::cout << ++share_to_first_private << std::endl;  
  14.    }  
  15.   
  16.    return 0;  
  17. }  

    运行程序,可以看到每个线程对应的私有变量share_to_first_private都初始化为1,并且每次循环各自增加1.

6. lastprivate示例

   

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <iostream>   
  2. #include <omp.h>   
  3.   
  4. int main()  
  5. {  
  6.    //通过lastprivate修饰后在并行区域内变为私有变量,进入并行区域   
  7.     //后变为私有变量,进入并行区域后每个线程拥有该变量的拷贝,并且会初始化   
  8.     int share_to_last_private = 1;  
  9.   
  10.    std::cout << "Before: " << share_to_last_private << std::endl;  
  11. #pragma omp parallel for lastprivate(share_to_last_private)firstprivate(share_to_last_private)   
  12.    for(int i = 0; i < 11; ++i)  
  13.    {  
  14.       std::cout << ++share_to_last_private << std::endl;  
  15.    }  
  16.   
  17.    std::cout << "After: " << share_to_last_private << std::endl;  
  18.    return 0;  
  19. }  

同样,仍然需要通过firstprivate来初始化并行区域中的变量,否则运行会出错。

在运行前后,share_to_last_private变量的值变了,其值最后变成最后一次循环的值,即多个线程最后一次修改的share_to_last_private(是share_to_last_private的copy)值会赋给share_to_last_private.

7. shared示例

   

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <iostream>   
  2. #include <omp.h>   
  3.   
  4. int main()  
  5. {  
  6.    int sum = 0;  
  7.    std::cout << "Before: " << sum << std::endl;  
  8. #pragma omp parallel for shared(sum)   
  9.    for(int i = 0; i < 10; ++i)  
  10.    {  
  11.       sum += i;  
  12.       std::cout << sum << std::endl;  
  13.    }  
  14.    std::cout << "After: " << sum << std::endl;  
  15.    return 0;  
  16. }  

上面的代码中,sum本身就是共享的,这里的shared的声明作为演示用。上面的代码因为sum是共享的,多个线程对sum的操作会引起数据竞争,后续在做介绍。

8. reduction的用法

   

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include <iostream>   
  2. #include <omp.h>   
  3.   
  4. int main()  
  5. {  
  6.    int sum = 0;  
  7.    std::cout << "Before: " << sum << std::endl;  
  8.   
  9. #pragma omp parallel for reduction(+:sum)   
  10.    for(int i = 0; i < 10; ++i)  
  11.    {  
  12.       sum = sum + i;  
  13.       std::cout << sum << std::endl;  
  14.    }  
  15.   
  16.    std::cout << "After: " << sum << std::endl;  
  17.    return 0;  
  18. }  

其中sum是共享的,采用reduction之后,每个线程根据reduction(+:sum)的声明算出自己的sum,然后再将每个线程的sum加起来。

运行程序,发现第一个线程sum的值依次为0、1、3、6、10;第二个线程sum的值依次为5、11、18、26、35;最后10+35=45。

若果将其中reduction声明去掉,则会输出:

计算步骤如下:

第一个线程sum=0,第二个线程sum=5

第一个线程sum=2+12=14;第二个线程sum=7+14=21

第一个线程sum=3+21=24;第二个线程sum=8+24=32

第一个线程sum=4+32=36;第二个线程sum=9+36=45

尽管结果是对的,但是两个线程对共享的sum的操作时不确定的,会引发数据竞争,例如计算步骤可能如下:

第一个线程sum=0,第二个线程sum=5

第一个线程sum=1+5=6;第二个线程sum=6+6=12

第一个线程sum=2+12=14;第二个线程sum=7+14=21

第一个线程sum=3+21=24;第二个线程sum=8+21=29 //在第一个线程没有将sum更改为24时,第二个线程读取了sum的值

第一个线程sum=4+29=33;第二个线程sum=9+33=42 //导致结果错误。

9. reduction声明可以看作:

     1. 保证了对sum的原则操作

     2. 多个线程的执行结果通过reduction中声明的操作符进行计算,以加法操作符为例:

假设sum的初始化为10,reduction(+:sum)声明的并行区域中每个线程的sum初始化为0(规定),并行处理结束之后,会将sum的初始化值10以及每个线程所计算的sum值相加。

10. reduction的声明形式

      其具体如下:

      reduction(operator: val1, val2, ...)

其中operator以及约定变量的初始值如下:

      运算符                    数据类型                        默认初始值

      +                          整数、浮点                      0

      -                           整数、浮点                      0

      *                          整数、浮点                      1

      &                          整数                               所有位均为1

      |                           整数                               0

      ^                          整数                               0

      &&                        整数                               1

      ||                          整数                               0

原文地址:https://www.cnblogs.com/BIGFOOT/p/2162219.html