【问题标题】:What is the most correct way to generate random numbers in C with pthread用pthread在C中生成随机数的最正确方法是什么
【发布时间】:2011-12-09 12:08:14
【问题描述】:

我有几个线程同时运行,每个线程都必须生成随机数。我想了解是否有要遵循的模式,了解在主线程中使用 srand 初始化随机生成器是否正确,或者是否每个线程都必须初始化自己的随机生成器。似乎 rand/srand 没有被设计用于线程,我想知道如何同时处理线程和随机数。 谢谢

编辑:我需要纯随机数,但我也有兴趣为测试目的生成确定性序列。我在 linux 上,但我更喜欢编写尽可能可移植的代码。

【问题讨论】:

  • 您是否希望序列是确定性的? - stackoverflow.com/questions/6467585/…
  • 如无特殊要求,请使用rand_r。
  • rand() 在 linux 上是线程安全的,但 posix 并不要求如此,尽管 posix 为此目的提供了 rand_r。 glibc 对其 rand() 使用内部互斥锁 - 如果您的线程生成大量随机数,这可能会导致争用

标签: c multithreading random pthreads srand


【解决方案1】:

在 Linux 上,您可以将 rand_r() 用于普通的生成器,或者将 drand48_r() 函数用于更好的生成器。两者都是 rand() 和 drand48() 的线程安全替换,通过采用由当前状态组成的单个参数,而不是使用全局状态。

关于你关于初始化的问题,上面的两个生成器都允许你在你想要的任何点播种,所以你不必在产生线程之前就播种。

【讨论】:

  • rand_r 自 POSIX 2008 以来已过时。我不知道为什么,但很想这样做。
【解决方案2】:

在使用线程并进行例如模拟时,让随机生成器独立是非常重要的。首先,它们之间的依赖关系确实会影响您的结果,然后对随机生成器状态的访问控制机制很可能会减慢执行速度。

在 POSIX 系统(您似乎在哪里)有 *rand48 系列函数,其中 erand48、nrand48 和 jrand48 将随机生成器的状态作为输入值。因此,您可以轻松地在每个线程中拥有独立的状态。那些你可以用一个已知的数字(例如你的线程数)初始化的,你会有一个可重现的随机数序列。或者你用一些不可预测的东西来初始化它,比如当前时间和数字,以使每次执行都有不同的序列。

【讨论】:

    【解决方案3】:

    在 Windows 上,您可以使用线程安全的 rand_s() 函数。如果您已经在使用 Boost,那么 boost::random 就可以胜任(尽管我很欣赏它被标记为 C,而不是 C++)。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 对不起这里的“necroposting”,但我想问我是否可以在 srand_init(unsigned int* seed) 中修改 srand_init(void) 而不是 clock_gettime(CLOCK_REALTIME, &ts); r[0] = (uint64_t)(ts.tv_sec * 1.0e9 + ts.tv_nsec);只需输入 r[0]=uint64_t(*seed),这样我就可以随时选择如何初始化我的 rng。
      • 此外,我应该如何以线程安全的方式初始化这个 rng?我生成我的线程,然后更改种子并在每个线程中使用 srand_init(seed)?或者我可以在我的并行区域内安全地调用 rand_range_128(UINT128_MIN, UINT128_MAX)?
      【解决方案5】:

      在 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)

      【讨论】:

        猜你喜欢
        • 2012-03-17
        • 1970-01-01
        • 2014-12-20
        • 2012-02-05
        • 1970-01-01
        • 2020-12-01
        • 2013-04-14
        • 2010-10-12
        • 1970-01-01
        相关资源
        最近更新 更多