从多个线程读取数组时要注意啥?

Posted

技术标签:

【中文标题】从多个线程读取数组时要注意啥?【英文标题】:What to heed, when reading an array from multiple threads?从多个线程读取数组时要注意什么? 【发布时间】:2014-09-25 07:27:24 【问题描述】:

我想稍微了解一下 OpenMP,因为我想并行化一个巨大的循环。经过一番阅读(SO、Common OMP mistakes、tutorial 等),我已将下面给出的基本工作的 c/mex 代码作为第一步(对于第一个测试用例会产生不同的结果)。

第一个测试确实总结了结果值 - 函数serial, parallel -, 第二个从输入数组中获取值并将处理后的值写入输出数组 - 函数 serial_a, parallel_a

我的问题是:

    为什么第一次测试的结果不同,我。 e. serialparallel 的结果 令人惊讶的是,第二次测试成功了。我关心的是,如何处理可能被多个线程读取的内存(数组位置)?在示例中,这应该由 a[i])/cos(a[n-i] 模拟。 是否有一些简单的规则可以确定哪些变量要声明为 privatesharedreduction? 在这两种情况下int i 都在pragma 之外,但是第二个测试似乎产生了正确的结果。那么这样可以吗,还是将i 移到pragma omp parallel 区域as being said here? 关于发现错误的任何其他提示?

代码

#include "mex.h"
#include <math.h>
#include <omp.h>
#include <time.h>

double serial(int x)

    double sum=0;
    int i;

    for(i = 0; i<x; i++)
        sum += sin(x*i) / cos(x*i+1.0);
    
    return sum;


double parallel(int x)

    double sum=0;
    int i;

    #pragma omp parallel num_threads(6) shared(sum) //default(none) 
    
        //printf("    I'm thread no. %d\n", omp_get_thread_num());

        #pragma omp for private(i, x) reduction(+: sum)
        for(i = 0; i<x; i++)
            sum += sin(x*i) / cos(x*i+1.0);
        
    
    return sum;


void serial_a(double* a, int n, double* y2)

    int i;

    for(i = 0; i<n; i++)
         y2[i] = sin(a[i]) / cos(a[n-i]+1.0);
    


void parallel_a(double* a, int n, double* y2)

    int i;

    #pragma omp parallel num_threads(6)
           
        #pragma omp for private(i)
        for(i = 0; i<n; i++)
            y2[i] = sin(a[i]) / cos(a[n-i]+1.0);
        
    


void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])

    double sum, *y1, *y2, *a, s, p;
    int x, n, *d;

    /* Check for proper number of arguments. */
    if(nrhs!=2) 
        mexErrMsgTxt("Two inputs required.");
     else if(nlhs>2) 
        mexErrMsgTxt("Too many output arguments.");
    
    /* Get pointer to first input */
    x = (int)mxGetScalar(prhs[0]);

    /* Get pointer to second input */
    a = mxGetPr(prhs[1]);
    d = (int*)mxGetDimensions(prhs[1]);
    n = (int)d[1]; // row vector

    /* Create space for output */
    plhs[0] = mxCreateDoubleMatrix(2,1, mxREAL);
    plhs[1] = mxCreateDoubleMatrix(n,2, mxREAL);

    /* Get pointer to output array */
    y1 = mxGetPr(plhs[0]);
    y2 = mxGetPr(plhs[1]);

       /* Do the calculation */
        clock_t tic = clock();
        y1[0] = serial(x);
        s = (double) clock()-tic;
        printf("serial....: %.0f ms\n", s);
        mexEvalString("drawnow");

        tic = clock();
        y1[1] = parallel(x);
        p = (double) clock()-tic;
        printf("parallel..: %.0f ms\n", p);
        printf("ratio.....: %.2f \n", p/s);
        mexEvalString("drawnow");

        tic = clock();
        serial_a(a, n, y2);
        s = (double) clock()-tic;
        printf("serial_a..: %.0f ms\n", s);
        mexEvalString("drawnow");

        tic = clock();
        parallel_a(a, n, &y2[n]);
        p = (double) clock()-tic;
        printf("parallel_a: %.0f ms\n", p);
        printf("ratio.....: %.2f \n", p/s); 
    

输出

>> mex omp1.c
>> [a, b] = omp1(1e8, 1:1e8);
serial....: 13399 ms
parallel..: 2810 ms
ratio.....: 0.21 
serial_a..: 12840 ms
parallel_a: 2740 ms
ratio.....: 0.21 
>> a(1) == a(2)

ans =

     0

>> all(b(:,1) == b(:,2))

ans =

     1

系统

MATLAB Version: 8.0.0.783 (R2012b)
Operating System: Microsoft Windows 7 Version 6.1 (Build 7601: Service Pack 1)
Microsoft Visual Studio 2005 Version 8.0.50727.867

【问题讨论】:

【参考方案1】:

在您的函数parallel 中,您有一些错误。使用parallel 时应声明减少。使用parallel 时,还应声明私有和共享变量。但是,当您进行归约时,您不应将正在归约的变量声明为共享。减少将解决这个问题。

要知道要声明什么是私有的或共享的,您必须问自己正在写入哪些变量。如果一个变量没有被写入,那么通常你希望它被共享。在您的情况下,变量 x 不会更改,因此您应该将其声明为共享。但是,变量 i 确实会发生变化,因此通常您应该将其声明为私有,以便修复您可以执行的函数

#pragma omp parallel reduction(+:sum) private(i) shared(x)

    #pragma omp for 
    for(i = 0; i<x; i++)
        sum += sin(x*i) / cos(x*i+1.0);
    

但是,OpenMP 自动将并行区域的迭代器设为私有,并且在并行区域之外声明的变量默认共享,因此对于并行函数,您可以简单地执行

#pragma omp parallel for reduction(+:sum)
for(i = 0; i<x; i++)
    sum += sin(x*i) / cos(x*i+1.0);

请注意,此代码与您的序列号之间的唯一区别是编译指示语句。 OpenMP 旨在让您无需更改代码,除了编译指示语句。

当涉及到数组时,只要并行 for 循环的每次迭代都作用于不同的数组元素,那么您就不必担心共享和私有。因此,您可以将 private_a 函数简单地编写为

#pragma omp parallel for
for(i = 0; i<n; i++)
    y2[i] = sin(a[i]) / cos(a[n-i]+1.0);

除 pragma 语句外,它再次与您的 serial_a 函数相同。

但要小心假设迭代器是私有的。考虑以下双循环

for(i=0; i<n; i++) 
    for(j=0; j<m; j++) 
       //
    

如果您使用#pragma parallel for,则i 迭代器将被设为私有,但j 迭代器将被共享。这是因为parallel for 仅适用于i 上的外部循环,并且由于j 默认情况下是共享的,因此它不会设为私有。在这种情况下,您需要像 #pragma parallel for private(j) 一样显式声明 j 私有。

【讨论】:

只要并行 for 循环的每次迭代作用于不同的数组元素,那么您就不必担心共享和私有了。但是如果数组的相同元素必须由不同的线程读取呢? OMP 是否会处理这个问题,例如。 G。一个线程只是等待一个元素被另一个元素读取?或者阅读权限根本没有问题?那么如果一个元素可能必须由不同的线程更改呢?这可能吗? 如果数组的相同元素必须由不同的线程读取,您无需担心这一点。每个线程都会将数据拉入其本地缓存。但是,您必须担心是否有多个线程会写入同一个数组元素。这可能会导致竞争条件。您也可能在写入不同但在本地关闭的元素时遇到问题。这称为虚假分享。

以上是关于从多个线程读取数组时要注意啥?的主要内容,如果未能解决你的问题,请参考以下文章

我必须改变啥才能从数组中读取像素值?

多个线程从同一个文件中读取

如何从每个线程的数组中读取唯一元素?

从一个文件中读取同一行的线程

JAVA开启三个线程,去读取数组中的数据不能重复

从共享内存中读取 int 数组是不是会排除银行冲突?