C#随机数发生器线程安全吗?
C#的Random.Next()
方法是线程安全的吗?
Next
方法没有什么特别的做法来实现线程安全。 但是,这是一个实例方法。 如果您不在不同线程中共享“ Random
实例,则不必担心实例中的状态损坏。 不要在不同的线程中使用Random
的单个实例,而不要持有某种排他的锁。
Jon Skeet在这个问题上有几个很好的post:
StaticRandom
重新审视随机性
正如一些评论者所指出的那样,在使用Random
不同实例时,还存在另外一个潜在的问题,这些Random
实例是线程独占的,但是是相同的种子,因此它们可以产生相同的伪随机数序列,因为它们可能是同时创build的彼此在时间上接近。 缓解这个问题的一种方法是使用一个主要的Random
实例(被单个线程locking)来生成一些随机种子,并为每个其他线程初始化新的Random
实例。
不,从多个线程使用相同的实例可能会导致它中断并返回全0。 但是,创build一个线程安全的版本(每次调用Next()
)都不需要讨厌的锁)很简单。 从这篇文章中的想法改编而来:
public class ThreadSafeRandom { private static readonly Random _global = new Random(); [ThreadStatic] private static Random _local; public ThreadSafeRandom() { if (_local == null) { int seed; lock (_global) { seed = _global.Next(); } _local = new Random(seed); } } public int Next() { return _local.Next(); } }
这个想法是为每个线程保留一个单独的static Random
variables。 但是,这样做显然失败了,因为Random
的另一个问题 – 如果几乎同时创build多个实例(大约15ms内) ,它们将返回相同的值! 为了解决这个问题,我们创build一个全局静态的Random
实例来生成每个线程使用的种子。
顺便说一下,上面的文章有代码展示了Random
的这两个问题。
微软的官方答案是非常强大的。 从http://msdn.microsoft.com/en-us/library/system.random.aspx#8 :
随机对象不是线程安全的。 如果您的应用程序调用多个线程的随机方法,则必须使用同步对象来确保一次只有一个线程可以访问随机数生成器。 如果您不确保以线程安全的方式访问Random对象,则对返回随机数的方法的调用将返回0。
正如文档中所描述的,当多个线程使用相同的Random对象时,可能会发生一个非常讨厌的副作用:它只是停止工作。
(即有一个竞争条件,当触发时,来自'random.Next ….'方法的返回值对于所有后续调用将为0)。
不,它不是线程安全的。 如果您需要使用不同线程中的相同实例,则必须同步使用情况。
虽然我不明白为什么你会需要这个。 每个线程拥有自己的Random类实例会更有效率。
另一个线程安全的方法是使用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))); }
将适用于less量的线程。
对于它的价值,这是一个线程安全的,密码强大的RNG,它inheritance了Random
。
该实现包含易于使用的静态入口点,它们与公共实例方法具有相同的名称,但前缀为“Get”。
对RNGCryptoServiceProvider.GetBytes
的调用是一个相对昂贵的操作。 这可以通过使用内部缓冲区或“池”来降低,从而减less频繁使用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; } }
由于Random
不是线程安全的,因此每个线程应该有一个,而不是全局实例。 如果你担心同时播种这些多个Random
类(即通过DateTime.Now.Ticks
或类似的),你可以使用Guid
来播种它们中的每一个。 .NET Guid
生成器需要相当长的时间来确保不可重复的结果,因此:
var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
每个文档
此types的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。 任何实例成员不保证是线程安全的。
对于线程安全的随机数生成器,请看RNGCryptoServiceProvider 。 从文档:
线程安全
这种types是线程安全的。
更新:不是。 您需要在每次连续调用时重复使用Random实例,同时调用.Next()方法时locking某个“信号量”对象,或在每次调用时使用保证随机种子的新实例。 如同Yassir所build议的那样,你可以通过在.NET中使用密码来获得保证的不同种子。
传统的线程本地存储方法可以通过对种子使用无锁algorithm来改进。 下面是从Java的algorithm无耻地窃取(可能甚至改善 ):
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; } }