多线程程序每次运行时都会输出不同的结果

时间:2015-04-09 23:38:19

标签: c multithreading openmp

我一直在尝试创建一个多线程程序,从1到999计算3和5的倍数但我似乎无法在每次运行时将其设置为正确我得到一个不同的值我认为它可能与我使用10个线程的共享变量的事实有关,但我不知道如何解决这个问题。如果我从1到9计算3和5的倍数,该程序也能正常工作。

#include <stdlib.h>
#include <stdio.h>
#include <omp.h>
#include <string.h>

#define NUM_THREADS 10
#define MAX 1000

//finds multiples of 3 and 5 and sums up all of the multiples
int main(int argc, char ** argv)
{
    omp_set_num_threads(10);//set number of threads to be used in the parallel loop


    unsigned int NUMS[1000] = { 0 };
    int j = 0;

    #pragma omp parallel
    {

        int ID = omp_get_thread_num();//get thread ID

        int i;
        for(i = ID + 1;i < MAX; i+= NUM_THREADS)
        {
            if( i % 5 == 0 || i % 3 == 0)
            {
                NUMS[j++] = i;//Store Multiples of 3 and 5 in an array to sum up later  

            }   
        }


    }

    int i = 0;
    unsigned int total;
    for(i = 0; NUMS[i] != 0; i++)total += NUMS[i];//add up multiples of 3 and 5

    printf("Total : %d\n", total);
    return 0;
}

4 个答案:

答案 0 :(得分:2)

&#34; J ++&#34;不是原子操作。

这意味着&#34;获取名为j的存储位置中包含的值,在当前语句中使用它,向其中添加一个值,然后将其存储回它来自的相同位置&#34;。

(这是一个简单的答案。优化以及值是否保存在寄存器中可以并且将会更改事物。)

如果有多个线程同时对同一个变量执行此操作,则会得到不同且不可预测的结果。

您可以使用线程变量来解决这个问题。

答案 1 :(得分:1)

在您的代码中j是共享的inductive variable。您不能依赖于使用多个线程有效地使用共享归纳变量(使用atomic每次迭代都不是有效的。)

你可以找到一个不使用归纳变量的特殊解决方案(例如,使用wheel factorization和7个辐条{0,3,5,6,9,10,12}中的15个)或者你可以找到一个使用私有归纳变量的通用解决方案

#pragma omp parallel
{
    int k = 0;
    unsigned int NUMS_local[MAX] = {0};
    #pragma omp for schedule(static) nowait reduction(+:total)
    for(i=0; i<MAX; i++) {
        if(i%5==0 || i%3==0) {
            NUMS_local[k++] = i;
            total += i;
        }
    }
    #pragma omp for schedule(static) ordered
    for(i=0; i<omp_get_num_threads(); i++) {
        #pragma omp ordered
        { 
            memcpy(&NUMS[j], NUMS_local, sizeof *NUMS *k);
            j += k;
        }
    }
}

但是,此解决方案并未充分利用内存。一个更好的解决方案是使用来自C ++的std::vector,你可以在C中使用realloc来实现,但我不打算为你做。

编辑:

这是一个特殊的解决方案,它不使用使用车轮分解的共享归纳变量

int wheel[] = {0,3,5,6,9,10,12}; 
int n = MAX/15;
#pragma omp parallel for reduction(+:total)
for(int i=0; i<n; i++) {
    for(int k=0; k<7; k++) {
        NUMS[7*i + k] = 7*i + wheel[k];
        total += NUMS[7*i + k];
    }
}
//now clean up for MAX not a multiple of 15
int j = n*7;
for(int i=n*15; i<MAX; i++) {
    if(i%5==0 || i%3==0) {
        NUMS[j++] = i;
        total += i;
    }
}

编辑:没有关键部分(来自ordered子句)可以这样做。这并行memcpy,并且至少对共享数组更好地利用内存。

int *NUMS;
int *prefix;
int total=0, j;
#pragma omp parallel
{
    int i;
    int nthreads = omp_get_num_threads();
    int ithread  = omp_get_thread_num();
    #pragma omp single 
    {
        prefix = malloc(sizeof *prefix * (nthreads+1));
        prefix[0] = 0;
    }
    int k = 0;
    unsigned int NUMS_local[MAX] = {0};
    #pragma omp for schedule(static) nowait reduction(+:total)
    for(i=0; i<MAX; i++) {
        if(i%5==0 || i%3==0) {
            NUMS_local[k++] = i;
            total += i;
        }
    }
    prefix[ithread+1] = k;
    #pragma omp barrier
    #pragma omp single
    {
        for(i=1; i<nthreads+1; i++) prefix[i+1] += prefix[i];
        NUMS = malloc(sizeof *NUMS * prefix[nthreads]);
        j = prefix[nthreads];
    }
    memcpy(&NUMS[prefix[ithread]], NUMS_local, sizeof *NUMS *k);
}
free(prefix);

答案 2 :(得分:0)

这是典型的线程同步问题。您需要做的就是使用内核同步对象,以实现任何所需操作的原子性(在您的情况下递增变量j的值)。它将是互斥信号量事件对象,具体取决于您正在使用的操作系统。但无论您的开发环境如何,为了提供原子性,基本流逻辑应该类似于以下伪代码:

{
    lock(kernel_object)
    // ...
    // do your critical operation (increment your variable j in your case)
    // ++j;
    // ...
    unlock(kernel_object)
}

如果你正在使用Windows操作系统,那么环境提供了一些特殊的同步机制(例如: InterlockedIncrement CreateCriticalSection 等)。如果你& #39;在基于Unix / Linux的操作系统上工作,您可以使用 mutex 信号量内核同步对象。实际上所有这些同步机制都源于信号量的概念,它是由 Edsger W. Dijkstra在1960年代初期发明的

以下是一些基本示例:

<强>的Linux

#include <pthread.h>

pthread_mutex_t g_mutexObject = PTHREAD_MUTEX_INITIALIZER;

int main(int argc, char* argv[])
{

   // ...

   pthread_mutex_lock(&g_mutexObject);
   ++j;                                 // incrementing j atomically
   pthread_mutex_unlock(&g_mutexObject);

   // ...

   pthread_mutex_destroy(&g_mutexObject);

  // ...

   exit(EXIT_SUCCESS);
}

<强>窗

#include <Windows.h>

CRITICAL_SECTION g_csObject;

int main(void)
{
    // ...

    InitializeCriticalSection(&g_csObject);

    // ...

    EnterCriticalSection(&g_csObject);
    ++j;                                // incrementing j atomically       
    LeaveCriticalSection(&g_csObject);

    // ...

    DeleteCriticalSection(&g_csObject);

    // ...

    exit(EXIT_SUCCESS);
 }

或只是简单地说:

#include <Windows.h>

LONG volatile g_j;  // our little j must be volatile in here now

int main(void)
{
    // ...

    InterlockedIncrement(&g_j);        // incrementing j atomically

    // ...

    exit(EXIT_SUCCESS);
}

答案 3 :(得分:-1)

你遇到的问题是线程没有按顺序执行,因此最后一个线程可能没有按顺序读取值,因此你会覆盖错误的数据。

有一个表单可以设置循环中的线程,当它们使用openmp选项完成时会做一个很好的事情。你必须得到像这样的东西才能使用它。

#pragma omp parallel for reduction(+:sum)
for(k=0;k<num;k++)
{
    sum = sum + A[k]*B[k];
}
/* Fin del computo */
gettimeofday(&fin,NULL);

你需要做的就是把结果写在&#34; sum&#34;中,这是来自我做过的旧代码。

你有另一个选择是脏的。不管怎样,让线程等待,并使用对OS的调用。这比看起来容易。这将是一个解决方案。

#pragma omp parallel
    for(i = ID + 1;i < MAX; i+= NUM_THREADS)
    {
        printf("asdasdasdasdasdasdasdas");
        if( i % 5 == 0 || i % 3 == 0)
        {
            NUMS[j++] = i;//Store Multiples of 3 and 5 in an array to sum up later  

        }   
    }

但我建议您完全阅读openmp选项。