Blu*_*eft 76

不,使用来自多个线程的相同实例可能导致它中断并返回所有0.但是,创建一个线程安全的版本(每次调用时Next()都不需要讨厌的锁)很简单.改编自本文中的想法:

public class ThreadSafeRandom
{
    private static readonly Random _global = new Random();
    [ThreadStatic] private static Random _local;

    public int Next()
    {
        if (_local == null)
        {
            lock (_global)
            {
                if (_local == null)
                {
                    int seed = _global.Next();
                    _local = new Random(seed);
                }
            }
        }

        return _local.Next();
    }
}
Run Code Online (Sandbox Code Playgroud)

我们的想法是static Random为每个线程保留一个单独的变量.然而,以明显的方式做到这一点失败了,因为另一个问题Random- 如果几乎同时创建多个实例(在大约15ms内),它们将返回相同的值!为了解决这个问题,我们创建了一个全局静态Random实例来生成每个线程使用的种子.

顺便提一下,上面的文章中有代码证明了这两个问题Random.

  • 很好,但不喜欢你需要创建`ThreadSafeRandom`来使用它的方式.为什么不使用带有惰性getter的静态属性,其中包含目前的construtors代码.来自这里的想法:http://confluence.jetbrains.com/display/ReSharper/'ThreadStaticAttribute'+usage然后整个类可以是静态的. (12认同)
  • 如果您实际在多个线程中使用它,则代码将无法工作,因为每次访问时都不会创建_local:NullRefereceException. (6认同)
  • 在需要的时候可以随时添加了一层间接的,例如,添加一个`IRandom`,并重定向在运行时调用静态类,这将允许嘲弄.只是对我来说,所有的成员都是静态的,暗示我应该有一个静态类,它告诉了用户更多,它说,每个实例则不然,它是共享的随机数序列分开.这是我之前需要模拟静态框架类的方法. (3认同)
  • 正如前面的评论中所提到的,当从多个线程使用时,这种方法实际上不起作用.`_local`无法在构造函数中实例化. (3认同)
  • @weston:这通常被认为是一种反模式.除了失去所有OOP好处之外,主要原因是静态对象不能被[模拟](http://en.wikipedia.org/wiki/Mock_object),这对于使用它的任何类进行单元测试至关重要. (2认同)
  • 这是否意味着您反对所有静态类和静态公共方法? (2认同)
  • 我会谨慎使用这种方法。`Random` 只有大约 20 亿个可能的种子(确切地说,有 *2^31 + 1*,因为 `Random` 构造函数接受一个有符号整数,然后在使用它之前将符号扔掉);对于某些应用程序,即使不需要加密安全的随机性,两个线程生成相同“随机”序列的 20 亿分之一的机会也是不可取的。*递增*静态存储的先前种子对我来说似乎比像您在这里所做的那样随机生成种子更好的一般做法。 (2认同)
  • 这是解决方案,使用双重检查反模式,也可以引入初始错误,只是可能性较小 (2认同)
  • 为什么需要双锁?`_local` 是线程静态的,因此在等待锁时没有其他线程可以初始化它...按照相同的逻辑,`_local = new Random(seed);` 可以脱离锁。事实上,当前的 .NET core 实现同时做到了这两点:https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Random.cs#L124 (2认同)

Meh*_*ari 25

Next实现线程安全的方法没有什么特别之处.但是,它是一个实例方法.如果不共享Random不同线程的实例,则不必担心实例中的状态损坏.Random如果不保留某种独占锁,则不要在不同线程中使用单个实例.

Jon Skeet在这个主题上有几个不错的帖子:

StaticRandom
重新审视随机性

正如一些评论员所指出的那样,使用Random线程排除的不同实例存在另一个潜在的问题,但是它们是相同的种子,因此会导致相同的伪随机数序列,因为它们可能同时创建或在紧密时间内创建彼此接近.缓解该问题的一种方法是使用主Random实例(由单个线程锁定)来生成一些随机种子并Random为每个其他要使用的线程初始化新实例.

  • 我不知道为什么这被标记为答案!问:"Random.Next线程安全吗?" 答:"如果你只从一个线程使用它,那么它是线程安全的"....最糟糕的回答! (19认同)
  • *"如果你不在不同的线程上共享Random的实例,你就不必担心了."* - 这是错误的.由于创建了"Random"的方式,如果几乎同时在两个单独的线程上创建两个单独的"Random"实例,它们将具有相同的种子(因此返回相同的值).有关解决方法,请参阅[我的回答](http://stackoverflow.com/questions/3049467). (11认同)
  • @BlueRaja我专门针对单个实例中的州腐败问题.当然,正如您所提到的,跨越两个不同的"随机"实例的统计关系的正交问题需要进一步关注. (4认同)
  • “他们将拥有相同的种子”这已在 .Net Core 中得到修复。 (2认同)

JSW*_*ork 19

从微软的官方答案是非常强的没有.来自http://msdn.microsoft.com/en-us/library/system.random.aspx#8:

随机对象不是线程安全的.如果您的应用程序从多个线程调用随机方法,则必须使用同步对象以确保一次只有一个线程可以访问随机数生成器.如果不确保以线程安全的方式访问Random对象,则对返回随机数的方法的调用将返回0.

正如文档中所描述的,当多个线程使用相同的Random对象时,可能会发生非常讨厌的副作用:它只是停止工作.

(即存在一种竞争条件,当触发时,'random.Next ....'方法的返回值对于所有后续调用将为0.)

  • 使用随机对象的不同实例会产生更严重的副作用。它为多个线程返回相同的生成数。来自同一篇文章:“我们建议您创建一个 Random 实例来生成应用程序所需的所有随机数,而不是实例化单个 Random 对象。然而,随机对象不是线程安全的。 (3认同)

Evg*_*eny 19

C# 的 Random.Next() 方法线程安全吗?

正如之前所写,答案是否定的。然而,从.NET6开始,我们有了开箱即用的线程安全替代方案:Random.Shared.Next();

请参阅此处的详细信息


Guf*_*ffa 14

不,这不是线程安全的.如果需要使用来自不同线程的相同实例,则必须同步使用.

不过,我真的看不出你为什么会这么做的原因.每个线程拥有自己的Random类实例会更有效.

  • @JSWork:是的,如果线程同时启动,则会出现计时问题.您可以在主线程中使用一个Random对象来提供种子,以便为线程创建Random对象来绕过它. (3认同)

小智 8

另一种线程安全的方法是使用ThreadLocal<T>如下:

new ThreadLocal<Random>(() => new Random(GenerateSeed()));
Run Code Online (Sandbox Code Playgroud)

GenerateSeed()每次调用时,该方法都需要返回一个唯一值,以确保随机数序列在每个线程中都是唯一的.

static int SeedCount = 0;
static int GenerateSeed() { 
    return (int) ((DateTime.Now.Ticks << 4) + 
                   (Interlocked.Increment(ref SeedCount))); 
}
Run Code Online (Sandbox Code Playgroud)

适用于少量线程.

  • `++ SeedCount`引入了竞争条件.请改用"Interlocked.Increment". (4认同)
  • 我认为您可以将 Random 构造函数中对GenerateSeed() 的调用替换为: Guid.NewGuid().GetHashCode()) (2认同)

Gle*_*den 6

由于Random不是线程安全的,因此每个线程应该有一个,而不是全局实例.如果您担心这些多个Random类同时播种(即通过DateTime.Now.Ticks或类似),您可以使用Guids来播种每个类..NET Guid生成器需要相当长的时间来确保不可重复的结果,因此:

var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
Run Code Online (Sandbox Code Playgroud)

  • -1; 实际上,用NewGuid生成的GUID保证是唯一的,但是这些GUID的前4个字节(即[BitConverter.ToInt32`](https://msdn.microsoft.com/zh-cn/library/ system.bitconverter.toint32.aspx)不在)。作为一般原则,将GUID的子字符串视为唯一[是一个糟糕的主意](https://blogs.msdn.microsoft.com/oldnewthing/20080627-00/?p=21823)。 (2认同)
  • 在这种特殊情况下,唯一可以兑换这种方法的是,至少在Windows上,.NET的`Guid.NewGuid` [使用版本4 GUID](/sf/answers/193057861/),大部分是随机生成的。特别是,前32位是随机生成的,因此,您实际上只是在给您的“ Random”实例添加(可能是加密的?)随机数,发生冲突的几率为20亿分之一。但是,我花了数小时的研究才能确定这一点,但我仍然不知道.NET Core的`NewGuid()`在非Windows操作系统上的行为。 (2认同)

Oha*_*der 5

更新从.NET 6开始Random.Shared提供了内置的线程安全Random类型(使用ThreadStatic 幕后进行同步)。

原始答案使用以下命令重新实现 BlueRaja 的答案ThreadLocal

public static class ThreadSafeRandom
{
    private static readonly System.Random GlobalRandom = new Random();
    private static readonly ThreadLocal<Random> LocalRandom = new ThreadLocal<Random>(() => 
    {
        lock (GlobalRandom)
        {
            return new Random(GlobalRandom.Next());
        }
    });

    public static int Next(int min = 0, int max = Int32.MaxValue)
    {
        return LocalRandom.Value.Next(min, max);
    }
}
Run Code Online (Sandbox Code Playgroud)