La méthode Random.Next()
de C # est-elle thread-safe?
Il n'y a rien de spécial à faire dans la méthode Next
pour assurer la sécurité des threads. Cependant, c'est une méthode d'instance. Si vous ne partagez pas d'instances de Random
entre différents threads, vous n'avez pas à vous soucier de la corruption d'état au sein d'une instance. N'utilisez pas une seule instance de Random
sur différents threads sans détenir un verrou exclusif quelconque.
Jon Skeet a quelques bons articles sur ce sujet:
StaticRandom
Revisiter l'aléatoire
Comme l'ont noté certains commentateurs, il existe un autre problème potentiel dans l'utilisation de différentes instances de Random
qui sont exclusives aux threads, mais qui sont semées de manière identique et induisent donc les séquences identiques de nombres pseudo-aléatoires, car elles peuvent être créées au niveau en même temps ou à proximité temporelle étroite les uns des autres. Une façon de résoudre ce problème consiste à utiliser une instance principale Random
(qui est verrouillée par un seul thread) pour générer des graines aléatoires et initialiser de nouvelles instances Random
pour chaque autre thread à utiliser.
Non, l'utilisation de la même instance à partir de plusieurs threads peut provoquer sa rupture et renvoyer tous les 0. Cependant, créer une version thread-safe (sans avoir besoin de verrous désagréables à chaque appel à Next()
) est simple. Adapté de l'idée de cet 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();
}
}
L'idée est de garder un static Random
variable pour chaque thread. Cependant, cela échoue de manière évidente, en raison d'un autre problème avec Random
- si plusieurs instances sont créées presque en même temps (dans un délai d'environ 15 ms) , ils renverront tous les mêmes valeurs! Pour résoudre ce problème, nous créons une instance Random
globalement statique pour générer les graines utilisées par chaque thread.
Soit dit en passant, l'article ci-dessus contient du code illustrant ces deux problèmes avec Random
.
La réponse officielle de Microsoft est très forte non . De http://msdn.Microsoft.com/en-us/library/system.random.aspx#8 :
Les objets aléatoires ne sont pas thread-safe. Si votre application appelle des méthodes aléatoires à partir de plusieurs threads, vous devez utiliser un objet de synchronisation pour vous assurer qu'un seul thread peut accéder au générateur de nombres aléatoires à la fois. Si vous ne vous assurez pas que l'objet Random est accessible de manière thread-safe, les appels aux méthodes qui renvoient des nombres aléatoires renvoient 0.
Comme décrit dans la documentation, il y a un effet secondaire très désagréable qui peut se produire lorsque le même objet Random est utilisé par plusieurs threads: il cesse de fonctionner.
(c'est-à-dire qu'il existe une condition de concurrence critique qui, lorsqu'elle est déclenchée, renvoie la valeur des méthodes `` random.Next .... '' sera 0 pour tous les appels suivants.)
Non, ce n'est pas sûr pour les threads. Si vous devez utiliser la même instance à partir de différents threads, vous devez synchroniser l'utilisation.
Je ne vois pas vraiment pourquoi vous en auriez besoin. Il serait plus efficace pour chaque thread d'avoir sa propre instance de la classe Random.
Un autre moyen sûr pour les threads est d'utiliser ThreadLocal<T>
Comme suit:
new ThreadLocal<Random>(() => new Random(GenerateSeed()));
La méthode GenerateSeed()
devra renvoyer une valeur unique à chaque fois qu'elle sera appelée pour garantir que les séquences de nombres aléatoires sont uniques dans chaque thread.
static int SeedCount = 0;
static int GenerateSeed() {
return (int) ((DateTime.Now.Ticks << 4) +
(Interlocked.Increment(ref SeedCount)));
}
Fonctionne pour un petit nombre de threads.
Puisque Random
n'est pas adapté aux threads, vous devez en avoir un par thread, plutôt qu'une instance globale. Si vous craignez que ces multiples classes Random
soient amorcées en même temps (c'est-à-dire par DateTime.Now.Ticks
ou autre), vous pouvez utiliser Guid
s pour amorcer chacun d'eux. Le générateur .NET Guid
va très loin pour garantir des résultats non reproductibles, d'où:
var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
Pour ce que ça vaut, voici un RNG thread-safe, cryptographiquement fort qui hérite de Random
.
L'implémentation comprend des points d'entrée statiques pour faciliter l'utilisation, ils ont les mêmes noms que les méthodes d'instance publique mais sont préfixés avec "Get".
Un appel au RNGCryptoServiceProvider.GetBytes
est une opération relativement coûteuse. Ceci est atténué par l'utilisation d'un tampon interne ou "Pool" pour rendre l'utilisation moins fréquente et plus efficace de RNGCryptoServiceProvider
. S'il y a peu de générations dans un domaine d'application, cela peut être considéré comme une surcharge.
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;
}
}
Par documentation
Tous les membres statiques publics (partagés en Visual Basic) de ce type sont thread-safe. Les membres d'instance ne sont pas garantis pour être thread-safe.
Pour un générateur de nombres aléatoires sûr pour les threads, regardez RNGCryptoServiceProvider . De la documentation:
Sécurité des fils
Ce type est thread-safe.