C# 的 Random.Next()
方法是否支持多线程安全?
C# 的 Random.Next()
方法是否支持多线程安全?
Next()
时使用锁)很简单。这个想法改编自这篇文章。public class ThreadSafeRandom
{
private static readonly Random _global = new Random();
[ThreadStatic] private static Random _local;
public int Next()
{
if (_local == null)
{
int seed;
lock (_global)
{
seed = _global.Next();
}
_local = new Random(seed);
}
return _local.Next();
}
}
这个想法是为每个线程保留一个单独的 static Random
变量。然而,显而易见的方式会失败,因为 Random
存在另一个问题 - 如果在(大约15毫秒内)创建多个实例,则它们将返回相同的值!为了解决这个问题,我们创建了一个全局静态 Random
实例来生成每个线程使用的种子。
顺便说一下,上面的文章中有代码展示了 Random
的这两个问题。
ThreadSafeRandom
来使用它的方式。为什么不使用带有延迟加载器的静态属性,该属性包含当前构造器的代码。这个想法来自于这里:http://confluence.jetbrains.com/display/ReSharper/'ThreadStaticAttribute'+usage,然后整个类可以是静态的。 - weston_local
无法在构造函数中实例化。 - Alex在Next
方法中没有特别处理以实现线程安全性,但它是一个实例方法。如果您不在不同线程之间共享Random
实例,您就不必担心实例内部状态的破坏。不要在没有某种独占锁的情况下跨不同线程使用单个Random
实例。
Jon Skeet有几篇关于这个主题的好文章:
正如一些评论员所指出的,使用不同的、线程专用的Random
实例存在另一个潜在问题,即它们被相同的种子初始化,从而产生相同的伪随机数序列,因为它们可能在同一时间或彼此接近的时间内创建。缓解这个问题的一种方法是使用一个主Random
实例(由单个线程锁定)生成一些随机种子,并为每个其他线程使用初始化新的Random
实例。
Random
实例,那么您就不用担心太多。这是错误的。由于创建 Random
的方式,如果在两个不同的线程上几乎同时创建了两个单独的 Random
实例,它们将具有相同的种子(因此返回相同的值)。请参见我的答案以获取解决方法。 - BlueRaja - Danny PflughoeftRandom
实例之间的统计关系的正交问题需要更加小心处理。 - Mehrdad Afshari如文档所述,当多个线程使用相同的Random对象时,可能会发生非常严重的副作用:它就停止工作了。Random对象不是线程安全的。如果您的应用程序从多个线程调用Random方法,您必须使用同步对象来确保只有一个线程可以同时访问随机数生成器。如果您不确保以线程安全的方式访问Random对象,则返回随机数的方法调用会返回0。
我们建议您创建一个单独的 Random 实例来生成应用程序所需的所有随机数,而不是实例化各自的 Random 对象。然而,Random 对象不是线程安全的。
- AaA不,它不是线程安全的。如果你需要从不同的线程使用相同的实例,你必须同步使用。
虽然我确实看不出你为什么需要这样做。对于每个线程拥有自己的 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)));
}
适用于少量线程。
更新 从 .NET 6 开始,Random.Shared 提供了一个内置的线程安全 Random
类型(使用 ThreadStatic
在幕后 进行同步)。
原始答案 使用 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);
}
}
Lazy<Random> GlobalRandom
来避免显式的 lock
。 - Theodor ZouliasRandom
的2个线程... - Ohad SchneiderLazy<T>
类没有任何作用。 - Theodor Zoulias以下是一个线程安全的、具有密码学强度的随机数生成器,它继承了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();
吗? - Chris Marisic由于Random
不是线程安全的,您应该在每个线程中拥有一个而不是全局实例。如果您担心这些多个Random
类同时被种子化(即通过DateTime.Now.Ticks
等方式),您可以使用Guid
来为它们中的每一个提供种子。 .NET Guid
生成器会花费相当长的时间来确保结果不可重复,因此:
var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
NewGuid
生成的GUID几乎可以保证是唯一的,但是这些GUID的前4个字节(仅被BitConverter.ToInt32
使用)不是唯一的。一般原则是,将GUID的子字符串视为唯一的是一个糟糕的想法。 - Mark AmeryGuid.NewGuid
使用的是版本4 GUID(请参见https://dev59.com/CHE85IYBdhLWcg3wejfn#2757969),它们大多数是随机生成的。特别地,前32位是随机生成的,所以你基本上只是用一个(可能具有密码学安全性)随机数种子来初始化你的`Random`实例,并且有20亿分之1的碰撞机会。我花费了*几个小时*的时间来确定这一点,但我仍然不知道.NET Core的NewGuid()
在非Windows操作系统上的行为。 - Mark Ameryprivate 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();
然后在你的类内自由使用它:
int nextRandomValue = GetRandomNext(0, 10);
随机函数的签名可以根据需要而不同,例如:
private static Func<int> GetRandomFunc()
{
Random random = new Random();
object lockObject = new object();
return () =>
{
lock (lockObject)
{
return random.Next();
}
};
}