在C语言中,使用pthread生成随机数的最正确方法是什么?

8
我有几个线程同时运行,每个线程都必须生成随机数。我想了解是否有遵循的模式,以便了解是否应该在主线程中使用srand初始化随机生成器,还是每个线程都必须初始化自己的随机生成器。似乎rand/srand没有设计用于与线程一起使用,我想知道如何处理线程和随机数。 谢谢
编辑:我需要纯随机数字,但我也有兴趣生成确定性序列供测试之用。我在Linux上操作,但我更喜欢编写尽可能便携的代码。

2
你想要这个序列是确定性的还是非确定性的呢?-https://dev59.com/u1jUa4cB1Zd3GeqPSpbw#6467623 - Flexo
1
如果您没有特殊要求,请使用 rand_r - David Schwartz
1
rand() 在 Linux 上是线程安全的,但 POSIX 并不要求它必须如此,尽管 POSIX 为此提供了 rand_r。glibc 对其 rand() 使用内部互斥锁 - 如果您的线程生成大量随机数,则可能会导致争用。 - nos
5个回答

8
在Linux上,你可以使用rand_r()作为一个普通的生成器,或者使用drand48_r()函数来获得一个更好的生成器。这两个函数都是线程安全的替代rand()drand48()函数,通过只使用当前状态作为单一参数,而不是使用全局状态。
关于初始化问题,以上两个生成器都允许你在任何时候进行种子初始化,因此你不必在创建线程之前就对它们进行种子初始化。

1
rand_r自POSIX 2008以来已经过时。我不知道为什么,但很想知道。 - con-f-use

4
在处理线程并进行模拟等工作时,非常重要的一点是确保你的随机生成器是独立的。首先,它们之间的依赖关系可能会导致结果偏差,其次,用于访问随机生成器状态的控制机制很可能会减慢执行速度。
在POSIX系统上(您似乎正在使用该系统),有一个*rand48函数族,其中erand48、nrand48和jrand48将随机生成器的状态作为输入值。因此,您可以轻松地在每个线程中拥有独立的状态。您可以使用已知的数字(例如您的线程编号)初始化它们,并且您将获得可重现的随机数序列。或者您可以使用一些不可预测的东西来初始化它,比如当前时间和数字,以获得每次执行都会变化的序列。

1

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

rand.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;
}

test.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)编译。


抱歉在这里“刨坟”,但我想问一下是否可以修改srand_init(void)为srand_init(unsigned int* seed),并将 clock_gettime(CLOCK_REALTIME, &ts); r[0] = (uint64_t)(ts.tv_sec * 1.0e9 + ts.tv_nsec); 改为r[0]=uint64_t(*seed),以便我始终可以选择如何初始化我的rng。 - Francesco Di Lauro
此外,我应该如何以线程安全的方式初始化这个 rng?我生成了我的线程,然后更改种子并在每个线程中使用 srand_init(seed)?还是我可以在我的并行化区域内安全地调用 rand_range_128(UINT128_MIN, UINT128_MAX)? - Francesco Di Lauro

1
在Windows上,您可以使用线程安全的rand_s()函数。如果您已经在使用Boost,则boost::random是有竞争力的(尽管我知道这被标记为C而不是C ++)。

0
在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 );
}

在这里,我不确定该函数生成的数字是否符合正态分布,换句话说,该函数是否是一个有效的RNG(在范围(min,max)内),但至少对我来说,它可以用来编写需要一些随机数的简单基准测试。

正如您所看到的,该函数利用POSIX线程ID重新排列随机种子。这样做,每个线程都有自己的随机种子,而不是依赖于time(NULL)的全局状态。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接