【问题标题】:Random numbers and thread local storage随机数和线程本地存储
【发布时间】:2010-09-21 03:18:25
【问题描述】:

this question 被接受的答案,以及今天工作中的类似讨论让我想知道一些事情。

问题是关于如何在多线程程序中安全地生成随机数。公认的答案主张使用线程本地存储,有效地为每个线程创建一个随机数生成器。我想知道这是否真的是个好主意。

假设我们有两个线程同时启动(在多核系统上很可能)并且都调用默认的Random 构造函数来创建和初始化线程本地存储中的随机数生成器。由于他们没有传递种子参数,Random 使用系统时间作为种子。因此,两个随机数生成器都使用相同的种子进行了初始化。它们都将生成相同的随机数序列。

由于这些线程是从线程池中分配的,因此您无法将特定对象与特定线程相关联。或者,在上述问题的情况下,您无法保证哪个池线程将执行下一个请求。所以想象一下会发生以下情况:

At startup, two requests come in simultaneously.
Two threads are created, each initializing a random number generator with the same seed.

Each thread generates three random numbers.  They will be identical in both threads.

Next request comes in.  It's assigned to thread #1.
It generates a random number and exits.

Some period of time elapses.

Next request comes in.  It's assigned to thread #2.
It generates the same random number that thread #1 did just a while ago.

这可能会无限期地持续下去,尽管我怀疑它会不会那么糟糕。关键是两个线程具有相同的 PRNG,并且重复序列的可能性非常高。我知道PRNG中的P代表“伪”,但这有点多。

我认为多个线程很有可能用相同的种子值初始化Random 实例。如果发生这种情况,那么应用程序中至少某些事物的“随机性”将会受到影响。当然,其含义取决于应用程序。

我不知道的是,如果 PRNG 使用不同的种子进行初始化,这是否会使客户端看到的序列更随机、更随机或大致相同?也就是说,如果我要写:

var rnd1 = new Random(123);
var rnd2 = new Random(654);
for (int i = 0; i < OneMillion; ++i)
{
    numbers.Add(rnd1.Next());
 numbers.Add(rnd2.Next());
}

与仅从任一 PRNG 中生成 200 万个数字相比,我生成的数字序列会更多还是更少随机?

【问题讨论】:

  • 如果在每次运行时生成相同的随机数序列,对您的应用程序来说是否有问题?
  • @Nader:这个问题更多的是理论而不是实际,因为我并没有真正想到特定的应用程序。但是假设您的问题的答案是肯定的。
  • @sje397:更多.Net,我想说,虽然这个想法实际上并不是特定于平台的,因为任何使用线程池和线程本地存储的系统都会表现出这种行为。但由于我的示例是用 C# 编写的并且我使用 .Net 术语,所以我用 .net 标记了这个问题。
  • @Jim:那么在这种情况下,你不会想要对你的种子进行硬编码。否则,这是正确的想法......确保不同的生成器有不同的种子,你很高兴。或者,您可以使用不同的 RNG。
  • @Jim:它只适用于使用类似 RNG 的其他平台(即,仅依赖于提供的种子)。

标签: .net multithreading random


【解决方案1】:

随机级别应该大致相同,因为这两个系列都是由same algorithm 生成的。

你如何定义随机性?一个序列是否出现更随机可能完全取决于用户,以及应用程序对这些数字序列的作用。

如果您担心多个随机数生成器使用同一个种子,您可以随时从另一个单独的生成器生成的序列中为所有随机数生成器播种。这样一来,至少您的初始起点有些随意。

【讨论】:

  • 谢谢。我的“直觉”告诉我,它们应该大致相同。我想我应该写一些代码来测试它(使用熵估计)。我希望有人能给出答案。至于多线程生成随机数,在我描述的情况下,我更喜欢使用受锁保护的单个 PRNG,而不是 TLS 解决方案。
  • 也许这是另一个 SO 问题:“熵估计告诉我们随机类的随机性,给定不同的种子”:) 至于单个/多个 PRNG,一个受锁保护的 PRNG感觉——很简单。转为使用多个确实是一个性能决定。
【解决方案2】:

生成的数字仅与您提供的种子一样随机。如果两个线程以相同的种子结束,它们将具有完全相同的“随机”数字序列。

为了防止这种情况,使用同步来确保每个 TLS 存储的随机数生成器都被赋予一个唯一的种子。

private static object _sync = new object();
[ThreadStatic]
private static Random _rand;

...

if (_rand == null) {
    lock(_sync) {
        _rand = new Random(DateTime.Now.Ticks);
        Thread.Sleep(_rand.Next(0,3));
    }
}

还有其他方法可以确保种子是唯一的,无需休眠,但这是一种用于演示的简单方法。

另一种选择,我认为更好的选择是只使用一个随机数生成器并同步对它的调用。每个人都担心同步会导致性能差异,但除非您每毫秒生成数百个随机数生成器,否则同步不会增加任何明显的性能降级(在我的笔记本电脑上,我可以每毫秒获得并释放 17,000 次锁)。

【讨论】:

  • 谢谢,Sam,但我知道如何避免这个问题。我更想知道 1) 是否有问题; 2) 问题有多大。
  • 我同意您编辑的回复:忘记 TLS 并使用锁。除非它被争用,否则锁定开销在我的工作站上大约为 50 纳秒。如果 50 纳秒很重要,那么您可能需要解决更大的性能问题。
  • @Jim Mischel,从您的问题来看,您似乎在问两个随机数生成器在获得相同种子时是否会是随机的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
相关资源
最近更新 更多