我正在尝试使用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;
}
答案 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;
}