有没有替代方式来生成随机数而不使用时间作为种子?

33

我想在计算集群上同时运行多个实例的代码(大约2000个实例)。其工作方式是,我提交作业,集群将在节点定期开放时运行它们,每个节点可运行多个作业。这似乎会导致许多实例在随机数生成中使用时间种子时产生相同的值。

是否有简单的替代方案可供使用?可复制性和安全性并不重要,快速生成唯一种子很重要。最简单的方法是什么,如果可能的话,跨平台的方法会很好。


1
我对情况不太清楚。你是在重新播种吗? - Brian Kennedy
1
嗯,不用理会那个...它超时了或者出现了什么错误...这是我的真实回答:你可以尝试使用GUID(全局唯一标识符)来混淆,并用它们来生成种子。然而,如果这个群集只有一台机器,你可能会有很多公共的比特位。我可能会让它们每个人从一个单独的提供种子的进程中请求种子...根据时间生成随机数作为种子。然后,该进程会在那里等待种子请求,并以其序列中的下一个随机数作为响应进行回复。这样,你的2000个处理实际工作的进程就能得到一个漂亮、均匀分布的种子了。 - Brian Kennedy
1
使用时间,但添加一些与本地平台相关的内容,例如IP地址或当前线程的任务ID等。 - Hot Licks
正确答案是下面提到C++11的std::random_device来种子化随机数生成器。 - EmeryBerger
那不是正确的答案,因为这个问题是专门标记为 C 而不是 C++,尽管如果使用 C++11 的话对人们来说也是好事。 - CHP
显示剩余5条评论
10个回答

30

rdtsc指令是一个相当可靠(且随机)的种子。

在Windows中,可以通过__rdtsc()内建函数来访问该指令。

在GNU C中,可以通过以下方式访问:

unsigned long long rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((unsigned long long)hi << 32) | lo;
}

该指令测量自处理器上电以来的总伪周期数。鉴于当今机器的高频率,即使两个处理器在相同时间启动并以相同速度时钟定时,它们返回相同值的可能性极小。


我喜欢这个答案,但现在还不能尝试它。当集群登录节点再次启动并查看其效果时,我会尝试一下。这是每个核心都可以工作吗?每个节点有两个处理器,每个处理器有4个核心。如果我以单线程模式运行代码,则每个作业请求一个核心;如果我以多线程模式运行,则最多要求8个核心。 - CHP
2
你可以在同一处理器甚至同一核上同时调用它。从技术上讲,同一处理器上的核心会有些同步,但这仍然不会成为问题,因为正常的抖动会使得两个线程在同一周期内调用它的概率几乎为零。(即使它们在同一周期内被调用,也有充分的理由相信处理器会阻止其中一个直到另一个完成 - 因此它甚至可能防止它们向两个线程返回相同的值) - Mysticial
终于有机会试一下,看起来似乎解决了问题,进行了2000次模拟测试,没有一个相同的结果 :) 我需要再测试几次以确保它的可靠性,但它似乎是有效的。 - CHP
嘿,神秘的人,我按错了箭头,五分钟后才注意到!编辑你的帖子,我会把它改成+1! - brice

5

将PID和时间组合起来应该足以获得唯一的种子。这不是100%跨平台的,但是在*nix平台上使用getpid(3),在Windows上使用GetProcessId可以让你成功99.9%。下面这个示例代码可能会有所帮助:

srand((time(NULL) & 0xFFFF) | (getpid() << 16));

您也可以从*nix系统的/dev/urandom读取数据,但是Windows上没有相当于此的等效物。


1
我不是Windows程序员,但我认为CryptGenRandom是*nix上/dev/(u)random的近似Windows等效物。 - Ilmari Karonen
快速 #include <unistd.h>; 以防无法访问您提供的链接 getpid(3) - Hastur

5
unsigned seed;

read(open("/dev/urandom", O_RDONLY), &seed, sizeof seed);
srand(seed); // IRL, check for errors, close the fd, etc...

我建议您使用更好的随机数生成器。

5
我假设你已经有一些启动其他进程的过程。请将种子传递给该过程。然后,您可以让主进程为每个进程传入一个随机数作为其种子。这样,只选择了一个任意的种子... 您可以使用时间作为种子。
如果没有主进程启动其他进程,则如果每个进程至少具有唯一索引,那么您可以让一个进程在内存中(如果是共享内存)或文件中(如果是共享磁盘)生成一系列随机数,然后让每个进程拉出第index个随机数以用作其种子。
没有什么比来自单个种子的一系列随机数字更能给您提供种子的均匀分布了。

5
如果可以使用C++11,考虑使用 std::random_device。建议您观看链接以获得全面指南。
视频链接中提取要点:永远不应该使用srandrand,而应该使用std::random_devicestd::mt19937。对于大多数情况,以下内容是您想要的:
#include <iostream>
#include <random>
int main() {
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<int> dist(0,99);
    for (int i = 0; i < 16; i++) {
        std::cout << dist(mt) << " ";
    }
    std::cout << std::endl;
}

1

Windows

提供 CryptGenRandom()RtlGenRandom() 函数。它们将返回一个随机字节数组,您可以将其用作种子。

您可以在 msdn pages 上找到文档。

Linux / Unixes

在 Linux 上,您可以使用 Openssl 的 RAND_bytes() 函数获取一定数量的随机字节。默认情况下,它会使用 /dev/random

综合起来:

#ifdef _WIN32
  #include <NTSecAPI.h>
#else
  #include <openssl/rand.h> 
#endif

uint32_t get_seed(void)
{
  uint32_t seed = 0;

#ifdef _WIN32
  RtlGenRandom(&seed, sizeof(uint32_t) );
#else
  RAND_bytes(&seed, sizeof(uint32_t) ); 
#endif

  return seed;
}

请注意,OpenSSL默认提供了一个加密安全的伪随机数生成器,因此您可以直接使用它。更多信息在这里

1

与其使用 C 标准库的 time() 函数测量秒数,您是否可以改用处理器的计数器呢?大多数处理器都拥有一个自由运行的时钟周期计数器,例如 x86/x64 上有时间戳计数器

时间戳计数器是自 Pentium 以来在所有 x86 处理器上存在的 64 位寄存器。它记录从复位开始的时钟周期数。

(该页面还有许多不同平台访问此计数器的方式 - gcc/ms visual c 等)

请注意,时间戳计数器并非没有缺陷,它可能无法在处理器之间同步(您可能不关心应用程序)。并且节能功能可能会加速或减慢处理器速度(同样,您可能不关心)。


1

一个想法... 生成一个 GUID(16字节),并对它的4字节或8字节块进行求和(取决于种子期望的宽度),允许整数环绕。将结果用作种子。

GUID通常封装了生成它们的计算机的特征(例如MAC地址),这应该使得两台不同的计算机最终生成相同的随机序列的概率非常小。

显然,这并不具有可移植性,但是对于您的系统找到适当的API /库不应太难(例如Win32上的UuidCreate,Linux上的uuid_generate)。


0

假设您在一个相当 POSIX-ish 的系统上,应该有 clock_gettime。这将给出纳秒的当前时间,这意味着从实际目的来说不可能获得两次相同的值。(理论上,糟糕的实现可能会有较低的分辨率,例如只将毫秒乘以一百万,但即使像 Linux 这样的半好的系统也会给出真正的纳秒结果)。


0
如果唯一性很重要,您需要安排每个节点知道其他人已经声明了哪些ID。您可以通过协议询问“有人声明了ID x吗?”或事先安排每个节点拥有一些未分配给其他人的ID来实现这一点。
(GUID使用机器的MAC地址,因此属于“事先安排”类别。)
没有某种形式的协议,您将冒着两个节点声明相同ID的风险。

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