我有几个线程同时运行,每个线程都必须生成随机数。我想了解是否有要遵循的模式,了解在主线程中使用 srand 初始化随机生成器是否正确,或者是否每个线程都必须初始化自己的随机生成器。似乎 rand/srand 没有被设计为与线程一起使用,我想知道如何同时处理线程和随机数。谢谢
编辑:我需要纯随机数,但我也有兴趣为测试目的生成确定性序列。我在 linux 上,但我更喜欢编写尽可能可移植的代码。
我有几个线程同时运行,每个线程都必须生成随机数。我想了解是否有要遵循的模式,了解在主线程中使用 srand 初始化随机生成器是否正确,或者是否每个线程都必须初始化自己的随机生成器。似乎 rand/srand 没有被设计为与线程一起使用,我想知道如何同时处理线程和随机数。谢谢
编辑:我需要纯随机数,但我也有兴趣为测试目的生成确定性序列。我在 linux 上,但我更喜欢编写尽可能可移植的代码。
在 Linux 上,您可以将rand_r()用于平庸的生成器,或者将drand48_r()函数用于更好的生成器。rand()
两者都是and的线程安全替换drand48()
,通过采用由当前状态组成的单个参数,而不是使用全局状态。
关于您关于初始化的问题,上面的两个生成器都允许您在任何您想要的点播种,因此您不会被迫在生成线程之前播种它们。
在使用线程并进行例如模拟时,让随机生成器独立是非常重要的。首先,它们之间的依赖关系确实会使您的结果产生偏差,然后对随机生成器状态的访问控制机制很可能会减慢执行速度。
在 POSIX 系统(您似乎在哪里)上,有*rand48
函数族 where erand48
,nrand48
并将jrand48
随机生成器的状态作为输入值。因此,您可以轻松地在每个线程中拥有独立的状态。那些你可以用一个已知的数字(例如你的线程数)初始化的,你会有一个可重现的随机数序列。或者你用一些不可预测的东西来初始化它,比如当前时间和数字,以使每次执行都有不同的序列。
在 Windows 上,您可以使用线程安全的rand_s()函数。如果您已经在使用 Boost,那么boost::random就可以胜任(尽管我很欣赏这是标记为 C,而不是 C++)。
rand_r 是线程安全的,但也是可重入的。
下面的代码使用 xorshift 算法生成 uint128_t 伪随机数。
附加属性:
uintx_types.h:
#ifndef UINTX_TYPES_H_INCLUDED
#define UINTX_TYPES_H_INCLUDED
#include <inttypes.h>
#include <ctype.h>
typedef __uint128_t uint128_t;
typedef __uint64_t uint64_t;
#define UINT128_C(hi, lo) (((uint128_t)(hi) << 64) | (uint128_t)(lo))
#define UINT128_MIN UINT128_C( 0x0000000000000000, 0x0000000000000000 )
#define UINT128_0 UINT128_MIN
#define UINT128_MAX (~(UINT128_0) - 1)
#endif // UINTX_TYPES_H_INCLUDED
lf.h:
#ifndef LF_H_INCLUDED
#define LF_H_INCLUDED
#define AAF(ADDR, VAL) __sync_add_and_fetch((ADDR), (VAL))
#endif // LF_H_INCLUDED
兰德.h:
#ifndef RAND_H_INCLUDED
#define RAND_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include "lf.h"
#include "uintx_types.h"
#define URANDOM "/dev/random"
void srand_init(void);
uint128_t rand_range_128(uint128_t min, uint128_t max);
#endif // RAND_H_INCLUDED
rand.c:
#include "rand.h"
uint64_t r[2];
uint64_t xorshift64star(int index)
{
uint64_t x;
x = r[index];
x ^= x >> 12; // a
x ^= x << 25; // b
x ^= x >> 27; // c
x = x * UINT64_C(2685821657736338717);
return AAF(&r[index], x);
}
void srand_init(void)
{
struct timespec ts;
size_t nbytes;
ssize_t bytes_read;
int fd;
clock_gettime(CLOCK_REALTIME, &ts);
r[0] = (uint64_t)(ts.tv_sec * 1.0e9 + ts.tv_nsec);
xorshift64star(0);
if ((fd = open(URANDOM, O_RDONLY, S_IRUSR | S_IRGRP | S_IROTH)) == -1)
{
r[1] = r[0] + 1;
xorshift64star(1);
}
else
{
nbytes = sizeof(r[1]);
bytes_read = read(fd, &r[1], nbytes);
if ((bytes_read == 0) || (r[1] == 0ull))
{
r[1] = r[0] + 1;
xorshift64star(1);
}
close(fd);
}
}
uint64_t rand_64(void)
{
return xorshift64star(0);
}
uint128_t rand_128(void)
{
uint128_t r;
r = xorshift64star(0);
r = (r << 64) | xorshift64star(1);
return r;
}
uint128_t rand_range_128(uint128_t min, uint128_t max)
{
return (rand_128() % (max+1-min))+min;
}
测试.c:
#define KEYS 1000
int main(int argc, char **argv)
{
int i;
uint128_t key;
srand_init();
for(i = 0; i <= KEYS; i++)
{
key = rand_range_128(UINT128_MIN, UINT128_MAX);
printf("%016"PRIx64"%016"PRIx64"\n", (uint64_t)(key >> 64), (uint64_t)key);
}
return 0;
}
在 Linux 下使用 gcc(4.9.2) 编译。
在 Linux 系统上,您可以使用如下函数:
size_t random_between_range( size_t min, size_t max ){
unsigned short state[3];
unsigned int seed = time(NULL) + (unsigned int) pthread_self();
memcpy(state, &seed, sizeof(seed));
return min + nrand48(state) % (max - min );
}
在这里我不得不说,我真的不知道这个函数生成的数字是否符合正态分布,换句话说,这个函数是否是 (min,max) 范围内的有效 RNG,但至少对我来说写一个简单的需要一些随机数的基准。
如您所见,该函数利用 POSIX 线程 ID 来重新排列随机种子。这样做,每个线程都有自己的随机种子,而不是使用全局状态,具体取决于time(NULL)