从多个线程使用 stdlib 的 rand()

Posted

技术标签:

【中文标题】从多个线程使用 stdlib 的 rand()【英文标题】:Using stdlib's rand() from multiple threads 【发布时间】:2011-09-03 21:59:10 【问题描述】:

我有几个线程都运行相同的功能。在每一个中,它们都会多次生成不同的随机数。我们试图通过将srand(time(0)) 放在函数的开头来做到这一点,但似乎它们都得到了相同的数字。

我们是否需要在每个程序中只调用一次srand(time(0)),即在main 的开头(例如),在每个被多次调用的函数的开头,还是其他什么地方?

【问题讨论】:

使用 C++0x 中的新随机数生成器可能会更好。你用的是什么编译器? 你使用的是什么操作系统 windows/linux ?? 如果所有的胎面都使用相同的 srand() 你会得到相同的随机数 不要从多个线程调用 rand()。使用 C++0x 中的随机数生成器。这些在 Boost 中也可用。 我观察到 rand() 每次线程启动时都会以相同的顺序启动。在我的应用程序中,我在一个循环中启动一个线程,然后 rand() 在每次迭代中重复相同的序列。 rand() 绝对是多线程的禁忌。我通过使用上面建议的那些较新的 C++ 生成器解决了这个问题。 【参考方案1】:

srand() 为随机数生成器提供种子。您应该只需要在启动期间调用一次srand(time(NULL))

也就是说,文档指出:

函数rand()不可重入的 或线程安全的,因为它使用隐藏 在每次调用时修改的状态。 这可能只是种子值 被下一次调用使用,或者它可能 是更精致的东西。为了 获得可重现的行为 线程应用程序,这种状态必须 明确。功能 rand_r() 提供了一个指向 unsigned int,用作状态。 这是一个非常少量的状态, 所以这个功能会很弱 伪随机发生器。尝试 drand48_r(3) 代替。

上面强调的部分可能是你所有线程得到相同数字的原因。

【讨论】:

目前尚不清楚这个非常狭窄的特定于实现的引用如何适用于原始问题。后者没有说明使用了什么实现。 rand() 可以轻松实现线程安全。这取决于具体的实现。 @AnT: 但是rand() 不是要求线程安全的,所以可移植的多线程程序不能安全地使用它(除非用互斥锁保护它或类似的)。 C17 7.22.2.1p3:“rand 函数不需要避免与其他对伪随机序列生成函数 [例如它自己] 的调用的数据竞争”。【参考方案2】:

由于您使用的是 C++ 而不是 C,因此您可以通过使用 c++11 来避免经常与 srand/rand 相关的线程问题。这取决于使用支持这些功能的最新编译器。您将在每个线程上使用单独的引擎和分发。 这个例子就像一个骰子。

#include <random>
#include <functional>

std::uniform_int_distribution<int> dice_distribution(1, 6);
std::mt19937 random_number_engine; // pseudorandom number generator
auto dice_roller = std::bind(dice_distribution, random_number_engine);
int random_roll = dice_roller();  // Generate one of the integers 1,2,3,4,5,6.

我在回答这个问题时提到了Wikipedia C++11 和Boost random。

【讨论】:

【参考方案3】:

来自rand 手册页:

函数 rand() 不是可重入的或线程安全的,因为它使用在每次调用时都会修改的隐藏状态。

所以不要将它与线程代码一起使用。使用rand_r(或者drand48_r,如果你使用的是linux/glibc)。为每个 RNG 播种不同的值(您可以在主线程中播种第一个 RNG 以为每个线程中的 RNG 生成随机种子)。

【讨论】:

有趣的是,rand 似乎是 glibc 中 rand_r 的线程安全版本,反之亦然(如果我正确解释了 the source code)。 你不是。您正在查看使其非线程安全的内部状态。【参考方案4】:

如果您同时启动所有线程,则发送到 srand 的时间对于每个线程可能是相同的。由于它们都具有相同的种子,因此它们都返回相同的序列。尝试使用其他东西,例如来自局部变量的内存地址。

【讨论】:

使用局部变量的内存地址并不是一个好的熵源。【参考方案5】:

C 不是为多线程设计的,因此 srand() 与多线程的行为没有定义,并且取决于 C 运行时库。

许多 Unix/Linux C 运行时库使用单一静态状态,从多个线程访问它是不安全的,因此对于这些 C 运行时,您根本不能在多个线程中使用 srand() 和 rand()。其他 Unix C 运行时的行为可能不同。

Visual C++ 运行时使用每个线程的内部状态,因此为每个线程调用 srand() 是安全的。但正如 Neil 指出的那样,您可能会为所有线程播种相同的值 - 所以使用 (time + thread-id) 播种。

当然,为了可移植性,请使用 Random 对象而不是 rand 函数,这样您就完全不依赖隐藏状态了。每个线程仍然需要一个对象,并且使用 (time + thread-id) 为每个对象播种仍然是一个好主意。

【讨论】:

我将评论“windows”,以便人们在 Windows 上下文中使用 MS Visual C++ 时更容易找到这个答案。【参考方案6】:

这是个好问题。我无法直接回答,因为我认为还有更大的问题。 无论如何,似乎都不清楚 rand 是否是线程安全的。它维护内部状态,如果它是每个进程或每个线程,它似乎并没有很好地定义,如果它是线程安全的,它是否是每个进程。

为了确保我会在每次访问周围锁定一个互斥锁。

或者最好使用定义更好的生成,例如来自boost的生成

【讨论】:

以上是关于从多个线程使用 stdlib 的 rand()的主要内容,如果未能解决你的问题,请参考以下文章

eclipse 调试模式下的 GDB 找不到 stdlib/rand.c

array_rand() 从数组中随机取出一个或多个单元

time.h中time(NULL),stdlib.h中srand(), rand()

RAND_MAX

在多个线程之间共享变量

stdlib.h