指定数组中线程访问的位置

时间:2013-11-13 12:48:04

标签: c openmp

我正在尝试创建一个创建数组的程序,并使用OpenMP为该数组中的每个位置分配值。这将是微不足道的,除了我想指定一个数组负责的位置。

例如,如果我有一个长度为80和8个线程的数组,我想确保线程0只写入位置0-9,线程1到10-19,依此类推。

我是OpenMP的新手,所以我尝试了以下内容:

#include <omp.h>
#include <stdio.h>
#define N       80

int main (int argc, char *argv[]) 
{
    int nthreads = 8, tid, i, base, a[N];

    #pragma omp parallel
    {
        tid = omp_get_thread_num();
        base = ((float)tid/(float)nthreads) * N;
        for (i = 0; i < N/nthreads; i++) {
            a[base + i] = 0;
            printf("%d %d\n", tid, base+i);
        }
    }
    return 0;
}

然而,正如我所料,该计划并未访问所有职位。每次运行时输出都不同,例如:

4 40
5 51
5 52
5 53
5 54
5 55
5 56
5 57
5 58
5 59
5 50
4 40
6 60
6 60
3 30
0 0
1 10

我想我错过了一个指令,但我不知道它是哪一个。

3 个答案:

答案 0 :(得分:1)

你错过了一点点。指令

#pragma omp parallel

仅告知运行时以下代码块将并行执行,主要由所有线程执行。但它没有指定跨线程共享工作,只是所有线程都要执行块。要分享工作,您的代码将需要另一个指令,如此

#pragma omp parallel
{
    #pragma omp for 
    ...

这是for指令,它跨线程分配工作。

但是,您在程序设计中犯了一个错误,这比您对OpenMP语法的不熟悉更严重。正如您所建议的那样,跨线程手动分解工作正是OpenMP旨在帮助程序员避免的。通过尝试自己进行分解,您正在编写OpenMP的编程并运行两个风险:

  1. 弄错了;尤其是错误的事情,编译器和运行时将会毫不费力地想到你的想法。
  2. 精心设计并行程序,其运行速度比串行程序慢。
  3. 如果您想对线程的工作分配进行一些控制,请调查schedule子句。我建议你开始你的并行区域(注意我将这两个指令融合到一个语句中):

    #pragma omp parallel for default(none) shared(a,base,N)
    {
        for (i = 0; i < N; i++) {
            a[base + i] = 0;
    }
    

    另请注意,我已指定变量的可访问性。这是一个很好的做法,特别是在学习OpenMP时。编译器会自动使i私有。

    正如我所写的那样,运行时将把i上的迭代分成块,每个线程一个。第一个帖子将获得i = 0..N/num_threads,第二个帖子将获得i = (N/num_threads)+1..2N/num_threads,依此类推。

    稍后您可以明确地向该指令添加schedule子句。我上面写的相当于

    #pragma omp parallel for default(none) shared(a,N) schedule(static)
    

    但您也可以尝试

    #pragma omp parallel for default(none) shared(a,N) schedule(dynamic,chunk_size)
    

    以及许多其他选项,这些选项在通常的地方都有很好的记录。

答案 1 :(得分:1)

确保事物以你想要的方式工作的方法是使用一个只有8次迭代的循环作为外部(并行)循环,并让每个线程执行一个内部循环来访问正确的元素:

#pragma omp parallel for private(j)
   for(i = 0; i < 8; i++) {
     for(j = 0; j < 10; j++) {
       a[10*i+j] = 0;
       printf("thread %d updated element %d\n", omp_get_thread_num(), 8*i+j);
     }
   }

我现在无法对此进行测试,但我90%确定这完全符合您的要求(当您这样做时,您可以“完全控制”工作原理)。然而,它可能不是最有效的事情。首先 - 当你只想将一堆元素设置为零时,你想要使用像memset这样的内置函数,而不是循环......

答案 2 :(得分:0)

#pragma omp parallel不足以使for循环并行化。

嗯......我注意到你真的试图手工分发工作。它不起作用的原因很可能是因为计算for循环参数的竞争条件。

如果我没记错的话,任何在并行区域之外声明的变量都会在线程之间共享。所以所有线程一次写入itidbase。您可以使用适当的private / shared条款。

然而,更好的方法是让OpenMP分配工作。 这就足够了:

#pragma omp parallel private(tid)
{
  tid = omp_get_thread_num();
  #pramga omp for
  for (i = 0; i < N; i++) {
    a[i] = 0;
    printf("%d %d\n", tid, i);
  }
}

请注意private(tid)它为每个线程制作tid的本地副本,因此它们不会在omp_get_thread_num()上相互覆盖。此外,可以声明shared(a),因为我们希望每个线程都在同一个表副本上工作。这是隐含的。我认为迭代器应该被声明为私有,但我认为pragma负责处理它,而不是100%它是如何在这个特定情况下,当它在并行区域之外声明时。但我确信你可以手动将它设置为shared然后搞砸了。

编辑:我注意到原始的潜在问题所以我拿出了不相关的部分。