„Alle öffentlichen statischen (in Visual Basic freigegebenen) Mitglieder dieses Typs sind threadsicher. Es wird nicht garantiert, dass Instanzmitglieder threadsicher sind. “ (aus den Dokumenten auf System.Random ). [OK, um fair zu sein: Das häufigste Problem mit Pseudozufallszahlen, das die Leute zu haben scheinen, wird auch dort erklärt und sie fragen immer noch]
Joey
Antworten:
32
Bei der NextMethode zur Gewährleistung der Gewindesicherheit wird nichts Besonderes getan . Es ist jedoch eine Instanzmethode. Wenn Sie Instanzen nicht Randomüber verschiedene Threads hinweg gemeinsam nutzen, müssen Sie sich keine Gedanken über die Statusbeschädigung innerhalb einer Instanz machen. Verwenden Sie keine einzelne Instanz Randomüber verschiedene Threads hinweg, ohne eine exklusive Sperre zu haben.
Jon Skeet hat ein paar nette Beiträge zu diesem Thema:
Wie von einigen Kommentatoren festgestellt, gibt es ein weiteres potenzielles Problem bei der Verwendung verschiedener Instanzen Random, die Thread-exklusiv sind, aber identisch ausgesät werden und daher die identischen Sequenzen von Pseudozufallszahlen induzieren, da sie zur gleichen Zeit oder innerhalb eines engen Zeitrahmens erstellt werden können Nähe voneinander. Eine Möglichkeit, dieses Problem zu beheben, besteht darin, eine Master- RandomInstanz (die von einem einzelnen Thread gesperrt wird) zu verwenden, um zufällige Startwerte zu generieren und neue RandomInstanzen für jeden anderen zu verwendenden Thread zu initialisieren .
"Wenn Sie Instanzen von Random nicht über verschiedene Threads hinweg teilen, müssen Sie sich keine Sorgen machen." - Das ist falsch. RandomWenn aufgrund der Art und Weise, wie erstellt wurde, zwei separate Instanzen von Randomauf zwei separaten Threads fast gleichzeitig erstellt werden, haben sie dieselben Startwerte (und geben somit dieselben Werte zurück). Siehe meine Antwort für eine Problemumgehung.
BlueRaja - Danny Pflughoeft
5
@BlueRaja Ich habe mich speziell mit dem Problem der staatlichen Korruption innerhalb einer einzelnen Instanz befasst. Wie Sie bereits erwähnt haben, Randomerfordert das orthogonale Problem der statistischen Beziehung zwischen zwei verschiedenen Instanzen natürlich weitere Sorgfalt.
Mehrdad Afshari
23
Ich habe keine Ahnung, warum dies als Antwort markiert ist! F: "Ist Random.Next-Thread sicher?" A: "Wenn Sie es nur von einem Thread aus verwenden, ist es ja threadsicher" .... Die schlechteste Antwort aller Zeiten!
Mick
Das Schlimmste, was Sie tun können, ist, Artikel außerhalb zu veröffentlichen, um eine Antwort zu geben, und die Antwort NICHT tatsächlich einzuschließen ... Besonders wenn es so einfach ist.
Anthony Nichols
"Sie werden die gleichen Samen haben" Dies wurde in .Net Core behoben.
Magnus
85
Nein, die Verwendung derselben Instanz aus mehreren Threads kann dazu führen, dass sie unterbrochen wird und alle Nullen zurückgibt. Das Erstellen einer thread-sicheren Version (ohne dass bei jedem Aufruf böse Sperren erforderlich sind Next()) ist jedoch einfach. Angepasst an die Idee in diesem Artikel :
publicclassThreadSafeRandom
{
privatestaticreadonly Random _global = new Random();
[ThreadStatic] privatestatic Random _local;
publicintNext()
{
if (_local == null)
{
lock (_global)
{
if (_local == null)
{
int seed = _global.Next();
_local = new Random(seed);
}
}
}
return _local.Next();
}
}
Die Idee ist, static Randomfür jeden Thread eine separate Variable zu behalten . Dies auf offensichtliche Weise zu tun, schlägt jedoch aufgrund eines anderen Problems fehl: RandomWenn mehrere Instanzen fast gleichzeitig (innerhalb von ca. 15 ms) erstellt werden , geben alle dieselben Werte zurück! Um dies zu beheben, erstellen wir eine global statische RandomInstanz, um die von jedem Thread verwendeten Seeds zu generieren.
Der obige Artikel enthält übrigens Code, der diese beiden Probleme mit demonstriert Random.
Schön, aber es gefällt dir nicht, wie du ein erstellen musst, um dies ThreadSafeRandomzu nutzen. Warum nicht eine statische Eigenschaft mit einem Lazy Getter verwenden, der derzeit den Construtors-Code enthält? Idee von hier: konfluenz.jetbrains.com/display/ReSharper/… Dann kann die gesamte Klasse statisch sein.
Weston
2
@weston: Das wird normalerweise als Anti-Pattern angesehen. Abgesehen davon, dass alle OOP-Vorteile verloren gehen, besteht der Hauptgrund darin, dass statische Objekte nicht verspottet werden können. Dies ist für das Testen von Klassen, die dies verwenden, von entscheidender Bedeutung.
BlueRaja - Danny Pflughoeft
3
Sie können bei Bedarf jederzeit eine Indirektionsebene hinzufügen, z. B. eine IRandomund eine Klasse, die Aufrufe zur Laufzeit an eine statische Datei umleitet. Dies würde das Verspotten ermöglichen. Nur für mich sind alle Mitglieder statisch, was bedeutet, dass ich eine statische Klasse haben sollte, es sagt dem Benutzer mehr, es sagt, dass jede Instanz keine separate Folge von Zufallszahlen ist, sie wird geteilt. Dies ist der Ansatz, den ich zuvor gewählt habe, wenn ich eine statische Framework-Klasse verspotten muss.
Weston
6
Code funktioniert nicht, wenn Sie ihn tatsächlich aus mehreren Threads verwenden, da _local nicht bei jedem Zugriff erstellt wird: NullRefereceException.
Laie
3
Wie bereits im Kommentar erwähnt, funktioniert dieser Ansatz nicht, wenn er von mehreren Threads aus verwendet wird. _localkann im Konstruktor nicht instanziiert werden.
Zufällige Objekte sind nicht threadsicher. Wenn Ihre App Zufallsmethoden aus mehreren Threads aufruft, müssen Sie ein Synchronisationsobjekt verwenden, um sicherzustellen, dass jeweils nur ein Thread auf den Zufallszahlengenerator zugreifen kann. Wenn Sie nicht sicherstellen, dass auf das Random-Objekt threadsicher zugegriffen wird, geben Aufrufe von Methoden, die Zufallszahlen zurückgeben, 0 zurück.
Wie in den Dokumenten beschrieben, gibt es einen sehr schlimmen Nebeneffekt, der auftreten kann, wenn dasselbe zufällige Objekt von mehreren Threads verwendet wird: Es funktioniert einfach nicht mehr.
(dh es gibt eine Race-Bedingung, bei deren Auslösung der Rückgabewert der Methoden 'random.Next ....' für alle nachfolgenden Aufrufe 0 ist.)
Es gibt einen schlimmeren Nebeneffekt bei der Verwendung verschiedener Instanzen von zufälligen Objekten. Es gibt dieselbe generierte Nummer für mehrere Threads zurück. aus dem gleichen Artikel:Instead of instantiating individual Random objects, we recommend that you create a single Random instance to generate all the random numbers needed by your app. However, Random objects are not thread safe.
Nein, es ist nicht threadsicher. Wenn Sie dieselbe Instanz aus verschiedenen Threads verwenden müssen, müssen Sie die Verwendung synchronisieren.
Ich kann jedoch keinen Grund erkennen, warum Sie das brauchen würden. Es wäre effizienter, wenn jeder Thread eine eigene Instanz der Random-Klasse hätte.
Dies kann Sie in den Arsch beißen, wenn Sie ein Unit-Testobjekt haben und Tonnen von Testobjekten gleichzeitig generieren möchten. Der Grund dafür ist, dass viele Leute Random aus Gründen der Benutzerfreundlichkeit zu einem globalen Objekt machen. Ich habe das einfach gemacht und rand.Next () hat immer wieder 0 als aa-Wert generiert.
JSWork
1
@ JSWork: Ich folge nicht wirklich dem, was du meinst. Worauf beziehen Sie sich, wenn Sie "dies" sagen? Wenn ich Ihren letzten Satz richtig verstehe, haben Sie über mehrere Threads hinweg auf das Objekt zugegriffen, ohne es zu synchronisieren, was das Ergebnis erklären würde.
Guffa
1
Du hast Recht. Ich entschuldige mich - ich habe das schlecht formuliert. Nicht das zu tun, was du erwähnt hast, kann dich in den Arsch beißen. Als Vorsichtsmaßnahme sollten Leser vorsichtig sein, wenn sie auch für jeden Thread ein neues zufälliges Objekt erstellen - zufällige Objekte verwenden die aktuelle Zeit als Startwert. Zwischen den Samenwechseln liegt eine Lücke von 10 ms.
JSWork
3
@JSWork: Ja, es gibt ein Zeitproblem, wenn die Threads gleichzeitig gestartet werden. Sie können ein Zufallsobjekt im Hauptthread verwenden, um Startwerte für die Erstellung von Zufallsobjekten bereitzustellen, damit die Threads dies umgehen können.
Guffa
1
Wenn Sie für jeden Thread einen separaten Zufall erstellen möchten, verwenden Sie für jeden Thread etwas Einzigartigeres für einen Startwert, z. B. Thread-ID oder Zeit + Thread-ID oder ähnliches.
Apokryfos
9
Eine andere thread-sichere Methode ist die Verwendung ThreadLocal<T>wie folgt:
new ThreadLocal<Random>(() => new Random(GenerateSeed()));
Die GenerateSeed()Methode muss bei jedem Aufruf einen eindeutigen Wert zurückgeben, um sicherzustellen, dass die Zufallszahlenfolgen in jedem Thread eindeutig sind.
In diesem Fall wird jedoch nicht sichergestellt, dass die Methode nicht threadsicher sein muss. Jeder Thread greift auf seine eigene Kopie des Objekts zu.
Lücke
4
++SeedCountführt eine Rennbedingung ein. Verwenden Sie Interlocked.Incrementstattdessen.
Edward Brey
1
Wie von OP festgestellt, funktioniert dies mit einer begrenzten Anzahl von Threads. Dies wäre wahrscheinlich eine schlechte Wahl innerhalb von ASP.NET
Chris Marisic,
1
Ich denke, Sie können den Aufruf von GenerateSeed () im Zufallskonstruktor durch ersetzen: Guid.NewGuid (). GetHashCode ())
Siavash Mortazavi
5
Da dies Randomnicht threadsicher ist, sollten Sie eine pro Thread anstelle einer globalen Instanz haben. Wenn Sie befürchten, dass diese mehreren RandomKlassen gleichzeitig ausgesät werden (dh von DateTime.Now.Ticksoder so), können Sie Guids verwenden, um jede von ihnen zu säen. Der .NET- GuidGenerator unternimmt erhebliche Anstrengungen, um nicht wiederholbare Ergebnisse sicherzustellen. Daher:
var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
-1; GUIDs, die mit generiert werden, NewGuidsind praktisch eindeutig, die ersten 4 Bytes dieser GUIDs (das ist alles, was BitConverter.ToInt32betrachtet wird) jedoch nicht. Grundsätzlich ist es eine schreckliche Idee , Teilzeichenfolgen von GUIDs als einzigartig zu behandeln .
Mark Amery
2
Das einzige, was diesen Ansatz in diesem speziellen Fall einlöst, ist, dass .NETs Guid.NewGuid, zumindest unter Windows, GUIDs der Version 4 verwenden , die meist zufällig generiert werden. Insbesondere werden die ersten 32 Bits zufällig generiert, sodass Sie Ihre RandomInstanz im Wesentlichen nur mit einer (vermutlich kryptografisch?) Zufallszahl mit einer Kollisionswahrscheinlichkeit von 1 zu 2 Milliarden versehen. Ich habe stundenlang nachgeforscht, um dies festzustellen, und ich habe immer noch keine Ahnung, wie sich .NET Core NewGuid()unter Nicht-Windows-Betriebssystemen verhält.
Mark Amery
@MarkAmery Das OP hat nicht angegeben, ob eine kryptografische Qualität erforderlich ist. Daher bin ich der Meinung, dass die Antwort als Einzeiler für die schnelle Codierung in unkritischen Situationen immer noch nützlich ist. Basierend auf Ihrem ersten Kommentar habe ich den Code geändert, um die ersten vier Bytes zu vermeiden.
Glenn Slayden
1
Die Verwendung der zweiten 4 Bytes anstelle der ersten 4 hilft nicht. Als ich sagte, dass die ersten 4 Bytes einer GUID nicht garantiert eindeutig sind, meinte ich das keine 4 Bytes einer GUID eindeutig sein sollen. Die gesamte 16-Byte-GUID ist, aber kein kleinerer Teil davon. Sie haben die Änderungen tatsächlich verschlimmert, da für GUIDs der Version 4 (von .NET unter Windows verwendet) die zweiten 4 Bytes 4 nicht zufällige Bits mit festen Werten enthalten. Ihre Bearbeitung hat die Anzahl der möglichen Startwerte auf die niedrigen Hunderte von Millionen reduziert.
Mark Amery
OK danke. Ich habe die Änderung rückgängig gemacht, und die Leute können Ihre Kommentare beachten, wenn sie die von Ihnen geäußerten Bedenken haben.
Glenn Slayden
4
Für was es wert ist, hier ist ein thread-sicheres, kryptografisch starkes RNG, das erbt Random .
Die Implementierung enthält statische Einstiegspunkte, um die Verwendung zu vereinfachen. Sie haben dieselben Namen wie die öffentlichen Instanzmethoden, jedoch mit dem Präfix "Get".
Ein Anruf bei der RNGCryptoServiceProvider.GetBytesist eine relativ teure Operation. Dies wird durch die Verwendung eines internen Puffers oder "Pools" gemindert, um eine weniger häufige und effizientere Nutzung zu ermöglichen RNGCryptoServiceProvider. Wenn eine Anwendungsdomäne nur wenige Generationen enthält, kann dies als Overhead angesehen werden.
using System;
using System.Security.Cryptography;
publicclassSafeRandom : Random
{
privateconstint PoolSize = 2048;
privatestaticreadonly Lazy<RandomNumberGenerator> Rng =
new Lazy<RandomNumberGenerator>(() => new RNGCryptoServiceProvider());
privatestaticreadonly Lazy<object> PositionLock =
new Lazy<object>(() => newobject());
privatestaticreadonly Lazy<byte[]> Pool =
new Lazy<byte[]>(() => GeneratePool(newbyte[PoolSize]));
privatestaticint bufferPosition;
publicstaticintGetNext()
{
while (true)
{
var result = (int)(GetRandomUInt32() & int.MaxValue);
if (result != int.MaxValue)
{
return result;
}
}
}
publicstaticintGetNext(int maxValue)
{
if (maxValue < 1)
{
thrownew ArgumentException(
"Must be greater than zero.",
"maxValue");
}
return GetNext(0, maxValue);
}
publicstaticintGetNext(int minValue, int maxValue)
{
constlong Max = 1 + (long)uint.MaxValue;
if (minValue >= maxValue)
{
thrownew 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));
}
}
}
publicstaticvoidGetNextBytes(byte[] buffer)
{
if (buffer == null)
{
thrownew 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);
}
}
publicstaticdoubleGetNextDouble()
{
return GetRandomUInt32() / (1.0 + uint.MaxValue);
}
publicoverrideintNext()
{
return GetNext();
}
publicoverrideintNext(int maxValue)
{
return GetNext(0, maxValue);
}
publicoverrideintNext(int minValue, int maxValue)
{
return GetNext(minValue, maxValue);
}
publicoverridevoidNextBytes(byte[] buffer)
{
GetNextBytes(buffer);
}
publicoverridedoubleNextDouble()
{
return GetNextDouble();
}
privatestaticbyte[] GeneratePool(byte[] buffer)
{
bufferPosition = 0;
Rng.Value.GetBytes(buffer);
return buffer;
}
privatestaticuintGetRandomUInt32()
{
uint result;
lock (PositionLock.Value)
{
if ((PoolSize - bufferPosition) < sizeof(uint))
{
GeneratePool(Pool.Value)
}
result = BitConverter.ToUInt32(
Pool.Value,
bufferPosition);
bufferPosition+= sizeof(uint);
}
return result;
}
}
Was ist der Zweck von PositionLock = new Lazy<object>(() => new object());? Sollte das nicht einfach so sein SyncRoot = new object();?
Chris Marisic
@ ChrisMarisic, ich folgte dem unten verlinkten Muster. Die verzögerte Instanziierung des Schlosses bietet jedoch nur einen minimalen Vorteil, sodass Ihr Vorschlag vernünftig erscheint. csharpindepth.com/articles/general/singleton.aspx#lazy
Jodrell
Dies sieht nach einer großartigen Lösung aus, aber ich habe einige Fragen, warum BitConverter.ToUInt32 im Vergleich zu BitConverter.ToInt32 verwendet wird, um GetNext () zu bereinigen. Und warum den Pool statisch machen? Es kann Sie vor mehreren Pools bewahren, aber in gleichzeitigen Systemen, in denen Sie viele SafeRandom-Instanzen haben, kann es auch zu einem Flaschenhals werden. Wie wird der RNGCryptoServiceProvider gesetzt?
Wouter
2
Pro Dokumentation
Alle öffentlichen statischen (in Visual Basic freigegebenen) Mitglieder dieses Typs sind threadsicher. Es wird nicht garantiert, dass Instanzmitglieder threadsicher sind.
Die von Ihnen zitierte Dokumentation ist tatsächlich falsch. Ich glaube, es ist ein maschinengenerierter Inhalt. Der Community-Inhalt zu diesem Thema auf MSDN enthält viele Informationen, warum der Zufallstyp nicht threadsicher ist und wie dieses Problem gelöst werden kann (entweder mit Kryptographie oder mithilfe von "Semaphoren")
1
@MTG Dein Kommentar ist verwirrt. Randomhat keine statischen Mitglieder, daher heißt es in den zitierten Dokumenten effektiv, dass nicht garantiert ist , dass alle Mitglieder threadsicher sind. . Sie geben an, dass dies falsch ist, und bestätigen dies, indem Sie sagen, dass ... Randomnicht threadsicher ist? Das ist so ziemlich das Gleiche, was die Ärzte gesagt haben!
Mark Amery
Pony Kopf auf dem Schreibtisch. Ja, Sie haben Recht. "Das ist so ziemlich das Gleiche, was die Dokumente gesagt haben!"
Seattle Leonard
2
Neuimplementierung der Antwort von BlueRaja mit ThreadLocal:
publicstaticclassThreadSafeRandom
{
privatestaticreadonly System.Random GlobalRandom = new Random();
privatestaticreadonlyThreadLocal<Random> LocalRandom = new ThreadLocal<Random>(() =>
{
lock (GlobalRandom)
{
returnnew Random(GlobalRandom.Next());
}
});
publicstaticintNext(int min = 0, int max = Int32.MaxValue)
{
return LocalRandom.Value.Next(min, max);
}
}
Ja, der RNGCryptoServiceProvider ist Gold. Viel besser als Random, obwohl es etwas mehr Beinarbeit gibt, um eine bestimmte Art von Zahl auszuspucken (da es zufällige Bytes erzeugt).
Rangoric
1
@ Rangoric: Nein, es ist nicht besser als Random für irgendeinen Zweck, bei dem beides verwendet werden kann. Wenn Sie Zufälligkeit für Verschlüsselungszwecke benötigen, ist die Zufallsklasse keine Option. Für jeden Zweck, den Sie auswählen können, ist die Zufallsklasse schneller und einfacher zu verwenden.
Guffa
@Guffa Benutzerfreundlichkeit ist eine einmalige Sache, da ich es bereits in eine Bibliothek gestopft habe, also nicht wirklich ein guter Punkt. Schneller zu sein ist ein gültiger Punkt, obwohl ich lieber die echte Zufälligkeit hätte als eine gute Zufälligkeit. Und dafür muss es auch threadsicher sein. Obwohl ich jetzt beabsichtige, dies zu testen, um zu sehen, wie viel langsamer es ist (Random erzeugt Doppel und konvertiert sie in das, was Sie verlangen, so dass es sogar davon abhängen kann, welchen Zahlenbereich Sie benötigen)
Rangoric
In C # stelle ich fest, dass es bei einer sehr einfachen RNGCrypto-Implementierung ungefähr 100: 1 ist, abhängig von der genauen Anzahl, nach der gesucht wird (127 ist beispielsweise doppelt so gut wie 128). Als nächstes plane ich, Threading hinzuzufügen und zu sehen, wie es funktioniert (warum nicht :))
Rangoric
Update auf obige Anweisung. Ich habe es für Bereiche unter 256 Werten auf 2: 1 gebracht und es ist besser, je näher wir an einem Faktor von 256 sind.
Rangoric
-1
AKTUALISIERT: Ist es nicht. Sie müssen entweder eine Instanz von Random bei jedem aufeinanderfolgenden Aufruf wiederverwenden, indem Sie beim Aufrufen der Methode .Next () ein "Semaphor" -Objekt sperren, oder bei jedem solchen Aufruf eine neue Instanz mit einem garantierten zufälligen Startwert verwenden. Sie können den garantierten unterschiedlichen Startwert erhalten, indem Sie die Kryptografie in .NET verwenden, wie von Yassir vorgeschlagen.
Der herkömmliche Ansatz zur lokalen Speicherung von Threads kann durch Verwendung eines Algorithmus ohne Sperre für den Startwert verbessert werden. Folgendes wurde schamlos aus Javas Algorithmus gestohlen (möglicherweise sogar verbessert ):
publicstaticclassRandomGen2
{
privatestaticreadonly ThreadLocal<Random> _rng =
new ThreadLocal<Random>(() => new Random(GetUniqueSeed()));
publicstaticintNext()
{
return _rng.Value.Next();
}
privateconstlong SeedFactor = 1181783497276652981L;
privatestaticlong _seed = 8682522807148012L;
publicstaticintGetUniqueSeed()
{
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;
}
}
Die (unvermeidliche) Besetzung intbesiegt den Punkt davon. Java's Randomwird mit a gesetzt long, aber C # nimmt nur ein int ... und noch schlimmer, es verwendet den absoluten Wert dieses signierten int als Seed, was bedeutet, dass es effektiv nur 2 ^ 31 verschiedene Seeds gibt. Eine gute longSamenerzeugung ist eine Art Verschwendung, wenn Sie dann die meisten Teile davon wegwerfen long. Zufälliges Seeding von C # ist zufällig, selbst wenn Ihr zufälliges Seed vollkommen zufällig ist, haben Sie dennoch eine Kollisionswahrscheinlichkeit von ungefähr 1 zu 2 Milliarden.
Antworten:
Bei der
Next
Methode zur Gewährleistung der Gewindesicherheit wird nichts Besonderes getan . Es ist jedoch eine Instanzmethode. Wenn Sie Instanzen nichtRandom
über verschiedene Threads hinweg gemeinsam nutzen, müssen Sie sich keine Gedanken über die Statusbeschädigung innerhalb einer Instanz machen. Verwenden Sie keine einzelne InstanzRandom
über verschiedene Threads hinweg, ohne eine exklusive Sperre zu haben.Jon Skeet hat ein paar nette Beiträge zu diesem Thema:
StaticRandom
Wiederholung der Zufälligkeit
Wie von einigen Kommentatoren festgestellt, gibt es ein weiteres potenzielles Problem bei der Verwendung verschiedener Instanzen
Random
, die Thread-exklusiv sind, aber identisch ausgesät werden und daher die identischen Sequenzen von Pseudozufallszahlen induzieren, da sie zur gleichen Zeit oder innerhalb eines engen Zeitrahmens erstellt werden können Nähe voneinander. Eine Möglichkeit, dieses Problem zu beheben, besteht darin, eine Master-Random
Instanz (die von einem einzelnen Thread gesperrt wird) zu verwenden, um zufällige Startwerte zu generieren und neueRandom
Instanzen für jeden anderen zu verwendenden Thread zu initialisieren .quelle
Random
Wenn aufgrund der Art und Weise, wie erstellt wurde, zwei separate Instanzen vonRandom
auf zwei separaten Threads fast gleichzeitig erstellt werden, haben sie dieselben Startwerte (und geben somit dieselben Werte zurück). Siehe meine Antwort für eine Problemumgehung.Random
erfordert das orthogonale Problem der statistischen Beziehung zwischen zwei verschiedenen Instanzen natürlich weitere Sorgfalt.Nein, die Verwendung derselben Instanz aus mehreren Threads kann dazu führen, dass sie unterbrochen wird und alle Nullen zurückgibt. Das Erstellen einer thread-sicheren Version (ohne dass bei jedem Aufruf böse Sperren erforderlich sind
Next()
) ist jedoch einfach. Angepasst an die Idee in diesem Artikel :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(); } }
Die Idee ist,
static Random
für jeden Thread eine separate Variable zu behalten . Dies auf offensichtliche Weise zu tun, schlägt jedoch aufgrund eines anderen Problems fehl:Random
Wenn mehrere Instanzen fast gleichzeitig (innerhalb von ca. 15 ms) erstellt werden , geben alle dieselben Werte zurück! Um dies zu beheben, erstellen wir eine global statischeRandom
Instanz, um die von jedem Thread verwendeten Seeds zu generieren.Der obige Artikel enthält übrigens Code, der diese beiden Probleme mit demonstriert
Random
.quelle
ThreadSafeRandom
zu nutzen. Warum nicht eine statische Eigenschaft mit einem Lazy Getter verwenden, der derzeit den Construtors-Code enthält? Idee von hier: konfluenz.jetbrains.com/display/ReSharper/… Dann kann die gesamte Klasse statisch sein.IRandom
und eine Klasse, die Aufrufe zur Laufzeit an eine statische Datei umleitet. Dies würde das Verspotten ermöglichen. Nur für mich sind alle Mitglieder statisch, was bedeutet, dass ich eine statische Klasse haben sollte, es sagt dem Benutzer mehr, es sagt, dass jede Instanz keine separate Folge von Zufallszahlen ist, sie wird geteilt. Dies ist der Ansatz, den ich zuvor gewählt habe, wenn ich eine statische Framework-Klasse verspotten muss._local
kann im Konstruktor nicht instanziiert werden.Die offizielle Antwort von Microsoft ist ein sehr starkes Nein . Von http://msdn.microsoft.com/en-us/library/system.random.aspx#8 :
Wie in den Dokumenten beschrieben, gibt es einen sehr schlimmen Nebeneffekt, der auftreten kann, wenn dasselbe zufällige Objekt von mehreren Threads verwendet wird: Es funktioniert einfach nicht mehr.
(dh es gibt eine Race-Bedingung, bei deren Auslösung der Rückgabewert der Methoden 'random.Next ....' für alle nachfolgenden Aufrufe 0 ist.)
quelle
Instead of instantiating individual Random objects, we recommend that you create a single Random instance to generate all the random numbers needed by your app. However, Random objects are not thread safe.
Nein, es ist nicht threadsicher. Wenn Sie dieselbe Instanz aus verschiedenen Threads verwenden müssen, müssen Sie die Verwendung synchronisieren.
Ich kann jedoch keinen Grund erkennen, warum Sie das brauchen würden. Es wäre effizienter, wenn jeder Thread eine eigene Instanz der Random-Klasse hätte.
quelle
Eine andere thread-sichere Methode ist die Verwendung
ThreadLocal<T>
wie folgt:new ThreadLocal<Random>(() => new Random(GenerateSeed()));
Die
GenerateSeed()
Methode muss bei jedem Aufruf einen eindeutigen Wert zurückgeben, um sicherzustellen, dass die Zufallszahlenfolgen in jedem Thread eindeutig sind.static int SeedCount = 0; static int GenerateSeed() { return (int) ((DateTime.Now.Ticks << 4) + (Interlocked.Increment(ref SeedCount))); }
Funktioniert für eine kleine Anzahl von Threads.
quelle
++SeedCount
führt eine Rennbedingung ein. Verwenden SieInterlocked.Increment
stattdessen.Da dies
Random
nicht threadsicher ist, sollten Sie eine pro Thread anstelle einer globalen Instanz haben. Wenn Sie befürchten, dass diese mehrerenRandom
Klassen gleichzeitig ausgesät werden (dh vonDateTime.Now.Ticks
oder so), können SieGuid
s verwenden, um jede von ihnen zu säen. Der .NET-Guid
Generator unternimmt erhebliche Anstrengungen, um nicht wiederholbare Ergebnisse sicherzustellen. Daher:var rnd = new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0))
quelle
NewGuid
sind praktisch eindeutig, die ersten 4 Bytes dieser GUIDs (das ist alles, wasBitConverter.ToInt32
betrachtet wird) jedoch nicht. Grundsätzlich ist es eine schreckliche Idee , Teilzeichenfolgen von GUIDs als einzigartig zu behandeln .Guid.NewGuid
, zumindest unter Windows, GUIDs der Version 4 verwenden , die meist zufällig generiert werden. Insbesondere werden die ersten 32 Bits zufällig generiert, sodass Sie IhreRandom
Instanz im Wesentlichen nur mit einer (vermutlich kryptografisch?) Zufallszahl mit einer Kollisionswahrscheinlichkeit von 1 zu 2 Milliarden versehen. Ich habe stundenlang nachgeforscht, um dies festzustellen, und ich habe immer noch keine Ahnung, wie sich .NET CoreNewGuid()
unter Nicht-Windows-Betriebssystemen verhält.Für was es wert ist, hier ist ein thread-sicheres, kryptografisch starkes RNG, das erbt
Random
.Die Implementierung enthält statische Einstiegspunkte, um die Verwendung zu vereinfachen. Sie haben dieselben Namen wie die öffentlichen Instanzmethoden, jedoch mit dem Präfix "Get".
Ein Anruf bei der
RNGCryptoServiceProvider.GetBytes
ist eine relativ teure Operation. Dies wird durch die Verwendung eines internen Puffers oder "Pools" gemindert, um eine weniger häufige und effizientere Nutzung zu ermöglichenRNGCryptoServiceProvider
. Wenn eine Anwendungsdomäne nur wenige Generationen enthält, kann dies als Overhead angesehen werden.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; } }
quelle
PositionLock = new Lazy<object>(() => new object());
? Sollte das nicht einfach so seinSyncRoot = new object();
?Pro Dokumentation
http://msdn.microsoft.com/en-us/library/system.random.aspx
quelle
Random
hat keine statischen Mitglieder, daher heißt es in den zitierten Dokumenten effektiv, dass nicht garantiert ist , dass alle Mitglieder threadsicher sind. . Sie geben an, dass dies falsch ist, und bestätigen dies, indem Sie sagen, dass ...Random
nicht threadsicher ist? Das ist so ziemlich das Gleiche, was die Ärzte gesagt haben!Neuimplementierung der Antwort von BlueRaja mit
ThreadLocal
: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); } }
quelle
Für einen thread sicheren Zufallszahlengenerator schauen Sie sich an RNGCryptoServiceProvider . Aus den Dokumenten:
quelle
AKTUALISIERT: Ist es nicht. Sie müssen entweder eine Instanz von Random bei jedem aufeinanderfolgenden Aufruf wiederverwenden, indem Sie beim Aufrufen der Methode .Next () ein "Semaphor" -Objekt sperren, oder bei jedem solchen Aufruf eine neue Instanz mit einem garantierten zufälligen Startwert verwenden. Sie können den garantierten unterschiedlichen Startwert erhalten, indem Sie die Kryptografie in .NET verwenden, wie von Yassir vorgeschlagen.
quelle
Der herkömmliche Ansatz zur lokalen Speicherung von Threads kann durch Verwendung eines Algorithmus ohne Sperre für den Startwert verbessert werden. Folgendes wurde schamlos aus Javas Algorithmus gestohlen (möglicherweise sogar verbessert ):
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; } }
quelle
int
besiegt den Punkt davon. Java'sRandom
wird mit a gesetztlong
, aber C # nimmt nur ein int ... und noch schlimmer, es verwendet den absoluten Wert dieses signierten int als Seed, was bedeutet, dass es effektiv nur 2 ^ 31 verschiedene Seeds gibt. Eine gutelong
Samenerzeugung ist eine Art Verschwendung, wenn Sie dann die meisten Teile davon wegwerfenlong
. Zufälliges Seeding von C # ist zufällig, selbst wenn Ihr zufälliges Seed vollkommen zufällig ist, haben Sie dennoch eine Kollisionswahrscheinlichkeit von ungefähr 1 zu 2 Milliarden.