在OMP中使用线程生成相同的随机数

时间:2018-10-19 00:27:57

标签: c multithreading random openmp

我正在尝试使用OMP对某些代码进行多线程处理。目前,我的顺序版本使用rand()来生成一组具有一致种子的随机数,以便每次运行时它们都返回相同的结果。我想并行化我的代码,但是rand()不是线程安全的。有人可以告诉我如何使用在线程上工作的随机数生成器,以便我可以在每次测试时生成相同的数据集,类似于使用带有rand()的种子的数据集。我的代码并行化如下:

    long linkCnt;
    long j;
    long t;
    srand(randInit);

    linkCnt=0; //Keep track of # of outgoing edges
 #pragma omp parallel for schedule(runtime) private(t)
    for(j = 0; j < G->N; j++)
    {

        if(i == j){
            t = NO_CONN;
        } else {
            t = (rand() % ((MAX_EDGE_WEIGTH-1) * 2)+1); //50% of having no connection
            if(t > MAX_EDGE_WEIGTH){
                //t = INF; //Like no connection
                t = NO_CONN; //Like no connection
            } else {
                linkCnt++;
                G->visited[j] = VISITED; //Do this to find isolated nods that have no incomming edge
            }
        }

        G->node[i][j] = t;
    }

2 个答案:

答案 0 :(得分:3)

在这里合并似乎有几个问题。

首先,rand()函数的非线程安全特性意味着与从顺序调用的同时,从不同的线程同时调用rand()可能会产生不同的值。用一个简单的例子来解释这个问题可能是最容易的,所以让我们看一下PCG的32位版本,因为它很好而且很简单。它具有32位状态,并生成如下的32位数字:

static uint32_t state = ...;

static uint32_t generate(void) {
  uint32_t s = state;
  uint32_t v = ((s >> ((s >> 28) + 4)) ^ s) * (277803737U);
  v ^= v >> 22;
  state = state * 747796405U + 1729U;
  return v;
}

现在考虑一下如果两个线程大致同时调用generate()会发生什么。也许它们对于state的值相同,因此两次生成相同的随机数。也许其中一个更新state会在另一个读取之前更新,因此它们会获得不同的值。

我们可以通过使用互斥量保护generate()函数,或者在使用32位PGC的情况下(这就是I use it用于可重现数字的原因))使用原子来保护该问题。如果这样做,我们将始终以相同的顺序获得相同的数字。

问题的第二部分是在代码中混合调用者的顺序时会发生什么。假设您有两个线程(称为A和B),并且每个线程必须运行两次循环。即使您是从线程安全的源中获取随机数,调用的顺序也可能是AABB,ABAB,ABBA,BBAA,BABA或BAAB,它们中的每一个都会导致您的代码生成不同的结果。 / p>

有几种解决方法。首先,您可以使用同步原语来确保每次迭代都按照您想要的顺序调用generate。最简单的方法可能是使用队列,但是您将浪费大量时间进行同步,并且会失去一些并行性的机会(并且必须大幅度地重组代码)。

如果迭代次数相对较少,则可以考虑提前生成数组。认为:

int i;
int nums[LEN];
for (i = 0 ; i < LEN ; i++)
  nums[i] = generate();
#pragma omp parallel for ...
for (i = 0 ; i < LEN ; i++) {
  do_stuff(nums[i]);
}

不过,更好的解决方案可能是完全放弃生成随机数的想法,而是使用散列。 https://burtleburtle.net/bob/hash/integer.html有一些选择。例如,

for (int i = 0 ; i < LEN ; i++) {
  do_stuff(hash(i));
}

您当然可以撒些盐,甚至可以使用rand()来产生盐。

答案 1 :(得分:1)

这是基于块的方法,该方法将问题空间划分为N / BLOCK_SIZE个块,并为每个块使用randInit +块号重新播种RNG。无论您拥有多少线程,这都能提供可重现的输出。它还为N + x的序列生成相同的初始N个数字。只要您保持相同的BLOCK_SIZE。

一个好的块大小可能类似于您的典型N /(max_num_procs * 2)。但是还有实验的空间。

SELECT e.*
FROM employees e
WHERE NOT EXISTS (
     select null 
     from team_employees te
     where te.id_employee = e.id AND te.id_team = 2
     )

具有不同N的输出,以及如下所示的线程数。

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

#define N_DEFAULT  48   //Default number of nodes 
#define BLOCK_SIZE 12   //BLOCK SIZE number of nodes per block.
                        //Changes this changes reseed frequencey, 
                        //.. and hence the generated sequence
#define randInit   42   //Had to be something.

int main(int argc , char* argv[])
{
    int N=N_DEFAULT;
    if (argc >1)
        N=atoi(argv[1]); 

    int rands[N];// keep our random numbers for sequential debug output
    int n=BLOCK_SIZE;
    int num_blocks=(N+BLOCK_SIZE-1)/ BLOCK_SIZE;  // ceil(N/BLOCK_SIZE)
    int nt=omp_get_max_threads();   


    printf("          N: %d\n",N);
    printf("     Blocks: %d, (size: %d)\n",num_blocks,n);
    printf("    threads: %d\n",nt);

    //Parallel random generation
    #pragma omp parallel for schedule(runtime) 
    for (int J=0;J<num_blocks;J++)
    {
        int block_seed=randInit+J;      // unique block seed
        int start = J * n;
        int end= start+n > N?N:start+n;

        int tid = omp_get_thread_num(); // Just for debug
        printf("thread %d: works on block %d (%d - %d )\n",tid,J,start,end);

        for (int j=start; j < end;j++)
        {           
            int t=rand_r(&block_seed); //rand_r provides thread safe (re-entrant rand)
            rands[j]=t;
        }
    }

    //Output for debug single thread
    for (int j=0; j < N;j++)
    {
        printf("%d : %d \n",j,rands[j]);
    }   
    return 0;
}