C#随机数生成器线程安全吗?

时间:2010-06-15 22:17:43

标签: c# random numbers

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

14 个答案:

答案 0 :(得分:72)

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

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();
    }
}

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

顺便提一下,上面的文章中的代码用Random来证明这两个问题。

答案 1 :(得分:25)

Next方法中没有做任何特殊的事情来实现线程安全。但是,它是一个实例方法。如果不跨不同线程共享Random的实例,则不必担心实例中的状态损坏。不要在不同的线程中使用Random的单个实例,而不要保留某种独占锁。

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

StaticRandom
Revisiting randomness

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

答案 2 :(得分:17)

微软的官方回答是非常。来自http://msdn.microsoft.com/en-us/library/system.random.aspx#8

  

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

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

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

答案 3 :(得分:14)

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

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

答案 4 :(得分:8)

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

new ThreadLocal<Random>(() => new Random(GenerateSeed()));

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

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

适用于少量线程。

答案 5 :(得分:5)

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

var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))

答案 6 :(得分:4)

对于它的价值,这里是一个线程安全,加密强的RNG,继承Random

实现包括易于使用的静态入口点,它们与公共实例方法具有相同的名称,但前缀为&#34; Get&#34;。

调用RNGCryptoServiceProvider.GetBytes是一项相对昂贵的操作。通过使用内部缓冲区或&#34; Pool&#34;来减轻这种情况。使用RNGCryptoServiceProvider的频率更低,效率更高。如果应用程序域中有几代,则可以将其视为开销。

using System;
using System.Security.Cryptography;

public class SafeRandom : Random
{
    private const int PoolSize = 2048;

    private static readonly Lazy<RandomNumberGenerator> Rng =
        new Lazy<RandomNumberGenerator>(() => new RNGCryptoServiceProvider());

    private static readonly Lazy<object> PositionLock =
        new Lazy<object>(() => new object());

    private static readonly Lazy<byte[]> Pool =
        new Lazy<byte[]>(() => GeneratePool(new byte[PoolSize]));

    private static int bufferPosition;

    public static int GetNext()
    {
        while (true)
        {
            var result = (int)(GetRandomUInt32() & int.MaxValue);

            if (result != int.MaxValue)
            {
                return result;
            }
        }
    }

    public static int GetNext(int maxValue)
    {
        if (maxValue < 1)
        {
            throw new ArgumentException(
                "Must be greater than zero.",
                "maxValue");
        }
        return GetNext(0, maxValue);
    }

    public static int GetNext(int minValue, int maxValue)
    {
        const long Max = 1 + (long)uint.MaxValue;

        if (minValue >= maxValue)
        {
            throw new ArgumentException(
                "minValue is greater than or equal to maxValue");
        }

        long diff = maxValue - minValue;
        var limit = Max - (Max % diff);

        while (true)
        {
            var rand = GetRandomUInt32();
            if (rand < limit)
            {
                return (int)(minValue + (rand % diff));
            }
        }
    }

    public static void GetNextBytes(byte[] buffer)
    {
        if (buffer == null)
        {
            throw new ArgumentNullException("buffer");
        }

        if (buffer.Length < PoolSize)
        {
            lock (PositionLock.Value)
            {
                if ((PoolSize - bufferPosition) < buffer.Length)
                {
                    GeneratePool(Pool.Value);
                }

                Buffer.BlockCopy(
                    Pool.Value,
                    bufferPosition,
                    buffer,
                    0,
                    buffer.Length);
                bufferPosition += buffer.Length;
            }
        }
        else
        {
            Rng.Value.GetBytes(buffer);
        }
    }

    public static double GetNextDouble()
    {
        return GetRandomUInt32() / (1.0 + uint.MaxValue);
    }

    public override int Next()
    {
        return GetNext();
    }

    public override int Next(int maxValue)
    {
        return GetNext(0, maxValue);
    }

    public override int Next(int minValue, int maxValue)
    {
        return GetNext(minValue, maxValue);
    }

    public override void NextBytes(byte[] buffer)
    {
        GetNextBytes(buffer);
    }

    public override double NextDouble()
    {
        return GetNextDouble();
    }

    private static byte[] GeneratePool(byte[] buffer)
    {
        bufferPosition = 0;
        Rng.Value.GetBytes(buffer);
        return buffer;
    }

    private static uint GetRandomUInt32()
    {
        uint result;
        lock (PositionLock.Value)
        {
            if ((PoolSize - bufferPosition) < sizeof(uint))
            {
                GeneratePool(Pool.Value)
            }

            result = BitConverter.ToUInt32(
                Pool.Value,
                bufferPosition);
            bufferPosition+= sizeof(uint);
        }

        return result;
    }
}

答案 7 :(得分:2)

每份文件

  

此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。不保证任何实例成员都是线程安全的。

http://msdn.microsoft.com/en-us/library/system.random.aspx

答案 8 :(得分:2)

使用ThreadLocal重新实现BlueRaja的答案:

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);
    }
}

答案 9 :(得分:1)

对于线程安全的随机数生成器,请查看RNGCryptoServiceProvider。来自文档:

  

线程安全

     

此类型是线程安全的。

答案 10 :(得分:1)

这是一个不涉及创建类的简单解决方案。将所有内容隐藏在临时 lambda 中:

return Email::with('attachments:url')->get(['email', 'subject', 'body', 'url']);

在任何需要线程安全随机生成器的类中定义

private static Func<int, int, int> GetRandomFunc()
{
    Random random = new Random();
    object lockObject = new object();

    return (min, max) =>
    {
        lock (lockObject)
        {
            return random.Next(min, max);
        }
    };
}

然后在你的课堂上自由使用它:

private static readonly Func<int, int, int> GetRandomNext = GetRandomFunc();

radndom 函数可以根据需要具有不同的签名。例如

int nextRandomValue = GetRandomNext(0, 10);

答案 11 :(得分:0)

我刚刚在一堆线程上同时创建了 Random 实例,并从结果中得到了意想不到的模式,因为大概所有的种子都是一样的。

我的解决方案是创建一个线程安全类 -

  public class StaticRandom
    {
        static Random rnd = new Random();

        public static int GetNext()
        {
            // random is not properly thread safe
            lock(rnd)
            {
                return rnd.Next();
            }
        }
    }

然后,为每个线程创建一个新的 Random 对象实例 -

// Take this threads instance seed from the static global version
Random rnd = new Random(StaticRandom.GetNext());

答案 12 :(得分:-1)

更新:事实并非如此。您需要在每次连续调用时重用Random实例,并在调用.Next()方法时锁定一些“信号量”对象,或者在每次调用时使用带有保证随机种子的新实例。正如Yassir建议的那样,你可以在.NET中使用加密技术来获得有保证的不同种子。

答案 13 :(得分:-1)

传统的thread local storage approach可以通过对种子使用无锁算法来改进。从Java的算法中可以无耻地窃取以下内容(甚至可能是improving):

public static class RandomGen2 
{
    private static readonly ThreadLocal<Random> _rng = 
                       new ThreadLocal<Random>(() => new Random(GetUniqueSeed()));

    public static int Next() 
    { 
        return _rng.Value.Next(); 
    } 

    private const long SeedFactor = 1181783497276652981L;
    private static long _seed = 8682522807148012L;

    public static int GetUniqueSeed()
    {
        long next, current;
        do
        {
            current = Interlocked.Read(ref _seed);
            next = current * SeedFactor;
        } while (Interlocked.CompareExchange(ref _seed, next, current) != current);
        return (int)next ^ Environment.TickCount;
   } 
}