zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    9 文件处理
    8 字符编码
    7 基础汇总
    6 元组和集合类型
    5 列表和字典类型
    4 数字和字符串类型
    3 条件语句与循环语句
    2 输入输出与运算符
    服务端如何识别是selenium在访问以及解决方案参考二
    服务端如何识别是selenium在访问以及解决方案参考一
  • 原文地址:https://www.cnblogs.com/BIGFOOT/p/2162219.html
Copyright © 2011-2022 走看看