Il thread del generatore di numeri casuali C # è sicuro?

Il metodo Random.Next() C # è thread-safe?

Non c’è nulla di speciale nel metodo Next per ottenere la sicurezza del thread. Tuttavia, è un metodo di istanza. Se non si condividono le istanze di Random su thread diversi, non è necessario preoccuparsi della corruzione dello stato all’interno di un’istanza. Non utilizzare una singola istanza di Random su thread diversi senza tenere un blocco esclusivo di qualche tipo.

Jon Skeet ha un paio di bei post su questo argomento:

StaticRandom
Rivisitazione della casualità

Come notato da alcuni commentatori, c’è un altro potenziale problema nell’usare diverse istanze di Random che sono esclusive del thread, ma sono seminate identicamente, e quindi inducono le sequenze identiche di numeri pseudocasuali, perché possono essere create nello stesso momento o all’interno di prossimità temporale reciproca. Un modo per alleviare il problema è utilizzare un’istanza Random master (bloccata da un singolo thread) per generare alcuni semi casuali e inizializzare le nuove istanze Random per ogni altro thread da utilizzare.

No, l’uso della stessa istanza da più thread può causare un’interruzione e il ripristino di tutti gli 0. Tuttavia, la creazione di una versione thread-safe (senza bisogno di blocchi fastidiosi su ogni chiamata a Next() ) è semplice. Adattato dall’idea in questo articolo :

 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’idea è di mantenere una variabile static Random separata per ogni thread. Tuttavia, ciò avviene in modo evidente a causa di un altro problema con Random : se più istanze vengono create quasi alla stessa ora (entro circa 15 ms) , restituiranno tutti gli stessi valori! Per risolvere questo problema, creiamo un’istanza Random globalmente statica per generare i semi utilizzati da ogni thread.

L’articolo sopra, a proposito, ha codice che dimostra entrambi questi problemi con Random .

La risposta ufficiale di Microsoft è un no molto forte. Da http://msdn.microsoft.com/en-us/library/system.random.aspx#8 :

Gli oggetti casuali non sono thread-safe. Se l’app richiama i metodi casuali da più thread, è necessario utilizzare un object di sincronizzazione per garantire che solo un thread possa accedere al generatore di numeri casuali alla volta. Se non si garantisce che l’object Random sia accessibile in modalità thread-safe, le chiamate ai metodi che restituiscono numeri casuali restituiscono 0.

Come descritto nei documenti, c’è un effetto collaterale molto spiacevole che può accadere quando lo stesso object Random viene utilizzato da più thread: smette semplicemente di funzionare.

(Esiste cioè una condizione di competizione che quando viene triggersta, il valore di ritorno dai metodi ‘random.Next ….’ sarà 0 per tutte le chiamate successive.)

No, non è sicuro. Se è necessario utilizzare la stessa istanza da thread diversi, è necessario sincronizzarne l’utilizzo.

Non riesco davvero a vedere alcun motivo per cui ne avresti bisogno, comunque. Sarebbe più efficiente per ogni thread avere la propria istanza della class Random.

Un altro modo sicuro per i thread è utilizzare ThreadLocal come segue:

 new ThreadLocal(() => new Random(GenerateSeed())); 

Il metodo GenerateSeed() dovrà restituire un valore univoco ogni volta che viene chiamato per assicurare che le sequenze numeriche casuali siano univoche in ciascun thread.

 static int SeedCount = 0; static int GenerateSeed() { return (int) ((DateTime.Now.Ticks << 4) + (Interlocked.Increment(ref SeedCount))); } 

Funzionerà per un piccolo numero di thread.

Dal momento che Random non è thread-safe, dovresti avere uno per thread, piuttosto che un’istanza globale. Se sei preoccupato che queste classi multiple di Random vengano seminate contemporaneamente (ad es. Per DateTime.Now.Ticks o simili), puoi usare Guid s per seminare ciascuna di esse. Il generatore di guida .NET fa di tutto per garantire risultati non ripetibili, quindi:

 var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0)) 

Per quello che vale, ecco un RNG crittograficamente sicuro e thread-safe che eredita Random .

L’implementazione include punti di ingresso statici per facilità d’uso, hanno lo stesso nome dei metodi di istanza pubblici ma sono preceduti da “Get”.

Una chiamata a RNGCryptoServiceProvider.GetBytes è un’operazione relativamente costosa. Ciò viene mitigato mediante l’uso di un buffer interno o di “Pool” per rendere meno frequente e più efficiente l’uso di RNGCryptoServiceProvider . Se ci sono poche generazioni in un dominio dell’applicazione, questo potrebbe essere visto come un sovraccarico.

 using System; using System.Security.Cryptography; public class SafeRandom : Random { private const int PoolSize = 2048; private static readonly Lazy Rng = new Lazy(() => new RNGCryptoServiceProvider()); private static readonly Lazy PositionLock = new Lazy(() => new object()); private static readonly Lazy Pool = new Lazy(() => 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; } } 

Per documentazione

Qualsiasi membro statico pubblico (Shared in Visual Basic) di questo tipo è thread-safe. Non è garantito che tutti i membri di istanza siano thread-safe.

http://msdn.microsoft.com/en-us/library/system.random.aspx

Per un generatore di numeri casuali thread safe guardare RNGCryptoServiceProvider . Dai documenti:

Filo di sicurezza

Questo tipo è thread-safe.

AGGIORNATO: non lo è. È necessario riutilizzare un’istanza di Random su ogni chiamata consecutiva bloccando un object “semaforo” mentre si chiama il metodo .Next () o utilizzare una nuova istanza con un seme casuale garantito su ciascuna di tali chiamate. Puoi ottenere il seme diverso garantito usando la crittografia in .NET come suggerito da Yassir.

Il tradizionale approccio di archiviazione locale del thread può essere migliorato utilizzando un algoritmo lock-less per il seed. Quanto segue è stato sottratto spudoratamente dall’algoritmo di Java (forse anche migliorandolo ):

 public static class RandomGen2 { private static readonly ThreadLocal _rng = new ThreadLocal(() => 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; } }