Est-ce que C# Random Number Generator thread est sûr?
11 réponses
rien de spécial n'est fait dans la méthode Next
pour assurer la sécurité du filetage. Cependant, c'est une méthode d'instance. Si vous ne partagez pas les instances de Random
entre différents threads, vous n'avez pas à vous soucier de la corruption d'état au sein d'une instance. Ne pas utiliser une seule instance de Random
à travers différents threads sans tenir une serrure exclusive d'une certaine sorte.
Jon Skeet a un couple de nice de posts sur ce sujet:
StaticRandom
Revisiter l'aléatoire
comme l'ont noté certains commentateurs, il y a un autre problème potentiel dans l'utilisation de différentes instances de Random
qui sont sans fil, mais sont semées de façon identique, et donc induire les séquences identiques de numéros de pseudorandom, parce qu'ils peuvent être créés au même moment ou à proximité temporelle de l'autre. Une façon de soulager ce problème est d'utiliser une instance maître 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, utiliser la même instance à partir de plusieurs threads peut la faire casser et retourner tous les 0. Cependant, créer une version sans fil (sans avoir besoin de serrures désagréables à chaque appel à Next()
) est simple. Adapté de l'idée dans cet article :
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();
}
}
l'idée est de conserver une variable séparée static Random
pour chaque fil. Le moyen évident échoue, cependant, en raison d'un autre problème avec Random
- si plusieurs instances sont créées à peu près en même temps (dans environ 15ms) , elles retourneront toutes les mêmes valeurs! Pour corriger cela, nous créons une instance globalement statique Random
pour générer les graines utilisées par chaque fil.
l'article ci-dessus, soit dit en passant, a un code démontrant ces deux questions avec Random
.
la réponse officielle de Microsoft est un très fort Non . De http://msdn.microsoft.com/en-us/library/system.random.aspx#8 :
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 le L'Objet aléatoire est accédé d'une manière sécurisée, les appels aux méthodes qui renvoient des nombres aléatoires renvoient 0.
comme décrit dans les docs, il y a un effet secondaire très désagréable qui peut se produire lorsque le même objet aléatoire est utilisé par plusieurs threads: il arrête tout simplement de fonctionner.
(c'est-à-dire qu'il y a une condition de course qui, lorsqu'elle est déclenchée, est la valeur de retour de l'aléatoire.Prochain....'les méthodes seront 0 pour tous les appels suivants.)
non, ce n'est pas sûr. Si vous avez besoin d'utiliser la même instance de différents threads, vous devez synchroniser l'utilisation.
Je ne vois pas pourquoi vous auriez besoin de ça. Il serait plus efficace que chaque thread ait sa propre instance de la classe aléatoire.
une autre façon sûre de filetage est d'utiliser ThreadLocal<T>
comme suit:
new ThreadLocal<Random>(() => new Random(GenerateSeed()));
la méthode GenerateSeed()
devra retourner une valeur unique chaque fois qu'elle est appelée pour s'assurer 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 fils.
puisque Random
n'est pas sans fil, vous devriez en avoir un par fil, plutôt qu'une instance globale. Si vous craignez que ces multiples classes Random
soient semées en même temps (c'est-à-dire par DateTime.Now.Ticks
ou autre), vous pouvez utiliser Guid
s pour ensemencer chacune d'elles. Le générateur .NET Guid
va à des longueurs considérables pour assurer des résultats non reproductibles, donc:
var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
pour ce que ça vaut, voici un RNG cryptographiquement fort et sans fil qui hérite de Random
.
la mise en œuvre inclut des points d'entrée statiques pour la facilité d'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. Cette situation est atténuée par l'utilisation d'un tampon interne ou D'un "Pool" pour réduire la fréquence et l'efficacité de RNGCryptoServiceProvider
. S'il y a peu de générations dans un domaine d'application, cela pourrait être considéré comme un frais généraux.
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 publics statiques (partagés dans Visual Basic) de ce type sont thread safe. Les membres de n'importe quelle instance ne sont pas garantis d'être thread safe.
pour un générateur de nombres aléatoires sans fil regardez RNGCryptoServiceProvider . Extrait du docs:
Sécurité Des Fils
ce type est sans fil.
mise à JOUR: Il ne l'est pas. Vous devez soit réutiliser une instance de Random sur chaque appel consécutif avec verrouillage d'un objet "sémaphore" lors de l'appel .Next () méthode ou utiliser une nouvelle instance avec une graine aléatoire garantie sur chaque tel appel. Vous pouvez obtenir la graine différente garantie en utilisant la cryptographie dans .NET comme Yassir suggéré.
il est possible d'améliorer la méthode traditionnelle de stockage local par filetage en utilisant un algorithme sans verrouillage pour la graine. Ce qui suit a été volé sans honte de L'algorithme de Java (peut-être même amélioration sur elle):
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;
}
}