C#的Random.Next()
方法线程安全吗?
C#的Random.Next()
方法线程安全吗?
Answers:
在Next
实现线程安全性的方法中,没有做任何特殊的事情。但是,这是一个实例方法。如果您不共享Random
不同线程之间的实例,则不必担心实例内的状态损坏。在未Random
持有某种排他锁的情况下,请勿在不同线程中使用单个实例。
乔恩·斯基特(Jon Skeet)在此主题上有几篇不错的文章:
正如一些评论员所指出的那样,在使用不同实例时存在另一个潜在问题,这些实例Random
是线程独占的,但它们的种子是相同的,因此会产生相同的伪随机数序列,因为它们可能同时创建或在短时间内创建彼此接近。缓解该问题的一种方法是使用主Random
实例(由单个线程锁定)来生成一些随机种子,并初始化Random
要使用的其他每个线程的新实例。
Random
创建方式不同,如果Random
几乎同时在两个单独的线程上创建两个单独的实例,则它们将具有相同的种子(并因此返回相同的值)。解决方法请参阅我的答案。
Random
实例之间的统计关系正交问题需要进一步注意。
不可以,在多个线程中使用同一实例可能会导致它中断并返回全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();
}
}
这个想法是static Random
为每个线程保留一个单独的变量。但是,由于存在另一个问题Random
-如果几乎同时(在大约15ms内)创建多个实例,则这样做显然会失败,它们都将返回相同的值!为了解决这个问题,我们创建了一个全局静态Random
实例来生成每个线程使用的种子。
顺便说一下,以上文章的代码演示了的这两个问题Random
。
ThreadSafeRandom
使用它的方式。为什么不将静态属性与包含当前construtors代码的惰性getter一起使用。这里的想法是:confluence.jetbrains.com/display/ReSharper / ...然后整个类可以是静态的。
IRandom
,以及一个在运行时将调用重定向到静态的类,这样可以进行模拟。对我来说,所有成员都是静态的,这意味着我应该有一个静态类,它告诉用户更多信息,它说每个实例不是独立的随机数序列,而是共享的。这是我之前需要模拟静态框架类时所采用的方法。
_local
不能在构造函数中实例化。
从微软的官方答案是非常强的没有。从http://msdn.microsoft.com/en-us/library/system.random.aspx#8:
随机对象不是线程安全的。如果您的应用程序从多个线程调用随机方法,则必须使用同步对象来确保一次只有一个线程可以访问随机数生成器。如果不确保以线程安全的方式访问Random对象,则对返回随机数的方法的调用将返回0。
如文档中所述,当多个线程使用同一个Random对象时,会发生非常讨厌的副作用:它只是停止工作。
(即,存在一种竞争条件,当触发该竞争条件时,所有后续调用的“ random.Next ....”方法的返回值将为0。)
Instead of instantiating individual Random objects, we recommend that you create a single Random instance to generate all the random numbers needed by your app. However, Random objects are not thread safe.
不,这不是线程安全的。如果需要使用来自不同线程的同一实例,则必须同步使用。
不过,我真的看不到您为什么需要它的任何原因。每个线程拥有自己的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)));
}
将适用于少量线程。
++SeedCount
介绍比赛条件。使用Interlocked.Increment
代替。
由于Random
不是线程安全的,因此每个线程应该有一个,而不是全局实例。如果您担心Random
同时播种这些多个类(即,按DateTime.Now.Ticks
此类),则可以使用Guid
s为它们中的每一个播种。.NETGuid
生成器会花费相当长的时间来确保不可重复的结果,因此:
var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
NewGuid
实际上,使用生成的GUID保证是唯一的,但是这些GUID的前4个字节(仅此而已BitConverter.ToInt32
)不是。作为一般原则,将GUID的子字符串视为唯一是一个可怕的想法。
Guid.NewGuid
至少在Windows上,.NET使用版本4 GUID,这些GUID大多是随机生成的。特别是,前32位是随机生成的,因此,您实际上只是在为您的Random
实例播种一个(大概是密码?)随机数,发生冲突的几率为20亿分之一。但是,我花了数小时的研究来确定这一点,而且我仍然不知道.NET CoreNewGuid()
在非Windows操作系统上的行为。
对于它的价值,这里有一个继承自线程安全,具有加密功能的RNG Random
。
该实现包括易于使用的静态入口点,它们具有与公共实例方法相同的名称,但前缀为“ Get”。
调用RNGCryptoServiceProvider.GetBytes
是相对昂贵的操作。通过使用内部缓冲区或“池”可以减少使用频率和效率,从而缓解这种情况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;
}
}
PositionLock = new Lazy<object>(() => new object());
什么?这不是SyncRoot = new object();
吗?
每个文档
此类型的任何公共static(在Visual Basic中为Shared)成员都是线程安全的。不保证任何实例成员都是线程安全的。
Random
没有静态成员,因此引用的文档有效地表明其所有成员“都不保证是线程安全的”。您指出这是不正确的,然后通过说...Random
不是线程安全的来对此进行支持。这与文档所说的差不多!
使用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);
}
}
对于线程安全的随机数生成器,请查看RNGCryptoServiceProvider。从文档:
线程安全
此类型是线程安全的。
传统的 通过对种子使用无锁算法,可以改进线程本地存储方法。从Java的算法中偷偷偷走了以下内容(甚至可能对其进行了改进):
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;
}
}
int
强制失败的观点。Java的Random
种子带有a long
,但是C#只是需要一个int ...而且更糟糕的是,它使用该有符号的int的绝对值作为种子,这意味着实际上只有2 ^ 31个不同的种子。long
如果您要丢掉大部分种子,那么产生好种子就是一种浪费long
; 随机地给C#随机种子,即使您的随机种子是完全随机的,仍然使您有大约20亿次碰撞的机会。