Random
non è thread-safe - non è necessario utilizzare la stessa istanza Random
da più thread senza sincronizzazione.
Perché stai ricevendo 1 in particolare? Bene, il modo in cui funziona Random
(in 4.5.2) consiste nel mantenere un array di seed e due indexer. Quando lo usi simultaneamente da più thread, il tuo seed array si incasina e quasi sempre ottieni valori identici in più slot. L'operazione di base fa qualcosa come seed[a] - seed[b]
e quando questi valori sono uguali, si ottiene lo zero indietro. Dal momento che hai chiesto 1 come minimo, questo zero è spostato su uno - e c'è la tua anomalia. Ciò avviene molto rapidamente in un ambiente multi-core, dal momento che c'è un bel po 'di stato interdipendente che viene aggiornato su ogni chiamata Next
.
Ci sono molti modi per risolvere questo problema. Uno è quello di sincronizzare l'accesso a un'istanza comune Random
- ha senso solo se stai facendo relativamente pochi randoms, però, nel qual caso non useresti comunque lo Parallel
. Se le prestazioni sono un problema, è necessario aggiungere qualche forma di pre-recupero (ad esempio, preparare i numeri casuali in batch, per thread o utilizzando una coda concorrente) o utilizzare qualche altro metodo.
Un altro modo è mantenere un'istanza separata Random
per ogni thread. Ciò richiede di selezionare attentamente un seme per ognuna delle istanze, tuttavia, altrimenti i numeri casuali potrebbero non essere casuali. L'approccio utilizzato in .NET stesso (ancora, usando il codice 4.5.2 come riferimento) è quello di utilizzare Thread.CurrentThread.ManagedThreadId
come seed, che funziona piuttosto bene. Un altro approccio comune consiste nell'utilizzare una singola istanza globale (sincronizzata) Random
per inizializzare i semi degli altri Random
s, ma a seconda delle esigenze, potrebbe essere necessario assicurarsi che non vengano prodotti semi duplicati.
Naturalmente, è possibile utilizzare anche un altro generatore di numeri casuali. Tuttavia, i generatori pseudo casuali di solito richiedono gli stessi approcci di Random
- dipendono fortemente dal loro stato; questo è ciò che li rende pseudo-casuali in primo luogo. Un generatore di crittografia potrebbe funzionare meglio, ma quelli tendono ad essere molto lenti e possono comunque ricorrere all'approccio sincronizzato, specialmente se non c'è supporto hardware.
In alcuni casi, ha senso distribuire i lavori di generazione secondo alcune regole ragionevoli. Ad esempio, se si utilizza la generazione procedurale pseudo-casuale per le risorse di gioco, può essere opportuno stabilire regole esplicite su come i generatori diversi vengono seminati, in modo ripetibile - naturalmente, questo significa anche che non si può realmente usare Parallel
entrambi, e dovrai essere un po 'più esplicito.
Il parallelo è davvero rilevante? Sei sicuro che questo non accada senza di esso? –
'Casuale' non è thread-safe. – Lee
@roryap Non succede con un ciclo normale. Almeno sulla mia macchina. –