Zufallszahlengenerator, der nur eine Zufallszahl generiert

765

Ich habe folgende Funktion:

//Function to get random number
public static int RandomNumber(int min, int max)
{
    Random random = new Random();
    return random.Next(min, max);
}

Wie ich es nenne:

byte[] mac = new byte[6];
for (int x = 0; x < 6; ++x)
    mac[x] = (byte)(Misc.RandomNumber((int)0xFFFF, (int)0xFFFFFF) % 256);

Wenn ich diese Schleife zur Laufzeit mit dem Debugger mache, erhalte ich unterschiedliche Werte (was ich will). Wenn ich jedoch einen Haltepunkt zwei Zeilen unter diesen Code setze, haben alle Mitglieder des macArrays den gleichen Wert.

Warum passiert das?

Ivan Prodanov
quelle
20
Verwenden new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);ergibt keine besseren "Zufallszahlen" als.Next(0, 256)
bohdan_trotsenko
Dieses NuGet-Paket ist möglicherweise hilfreich. Es bietet eine statische Rand.Next(int, int)Methode, die statischen Zugriff auf zufällige Werte bietet, ohne das Problem der Wiederverwendung von
Startwerten zu blockieren

Antworten:

1042

Jedes Mal, wenn Sie dies tun, wird new Random()es mit der Uhr initialisiert. Dies bedeutet, dass Sie in einer engen Schleife oft den gleichen Wert erhalten. Sie sollten eine einzelne Zufallsinstanz beibehalten und Next auf derselben Instanz verwenden.

//Function to get a random number 
private static readonly Random random = new Random(); 
private static readonly object syncLock = new object(); 
public static int RandomNumber(int min, int max)
{
    lock(syncLock) { // synchronize
        return random.Next(min, max);
    }
}

Bearbeiten (siehe Kommentare): Warum brauchen wir lockhier ein?

Grundsätzlich Nextwird sich der interne Status der RandomInstanz ändern . Wenn wir dies gleichzeitig aus mehreren Threads heraus tun, könnten Sie argumentieren, "wir haben das Ergebnis nur noch zufälliger gemacht", aber was wir tatsächlich tun, ist möglicherweise, die interne Implementierung zu brechen, und wir könnten auch anfangen, die gleichen Zahlen zu erhalten von verschiedenen Threads, die möglicherweise ein Problem sein - und vielleicht nicht. Die Garantie dafür, was intern passiert, ist jedoch das größere Problem. da Randomsich nicht machen keine Garantien für Thread-Sicherheit. Somit gibt es zwei gültige Ansätze:

  • Synchronisieren Sie, damit wir nicht gleichzeitig von verschiedenen Threads darauf zugreifen
  • Verwenden Sie unterschiedliche RandomInstanzen pro Thread

Beides kann in Ordnung sein; Das Stummschalten einer einzelnen Instanz von mehreren Anrufern gleichzeitig ist jedoch nur ein Problem .

Das lockerreicht den ersten (und einfacheren) dieser Ansätze; Ein anderer Ansatz könnte jedoch sein:

private static readonly ThreadLocal<Random> appRandom
     = new ThreadLocal<Random>(() => new Random());

Dies ist dann pro Thread, sodass Sie nicht synchronisieren müssen.

Marc Gravell
quelle
19
In der Regel sollten alle statischen Methoden threadsicher gemacht werden, da nur schwer garantiert werden kann, dass nicht mehrere Threads sie gleichzeitig aufrufen. Es ist normalerweise nicht erforderlich, Instanzmethoden (dh nicht statische Methoden) threadsicher zu machen .
Marc Gravell
5
@Florin - es gibt keinen Unterschied bezüglich "stapelbasiert" zwischen den beiden. Statische Felder sind genauso "externer Status" und werden absolut von Anrufern geteilt. Bei Instanzen besteht eine gute Chance, dass verschiedene Threads unterschiedliche Instanzen haben (ein gemeinsames Muster). Mit der Statik wird garantiert, dass sie alle gemeinsam nutzen (ohne [ThreadStatic]).
Marc Gravell
2
@gdoron bekommst du einen Fehler? Die "Sperre" soll verhindern, dass Fäden hier
übereinander stolpern
6
@ Dan, wenn das Objekt nie öffentlich zugänglich gemacht wird: Sie können. Das (sehr theoretische) Risiko besteht darin, dass ein anderer Thread ihn auf eine Weise blockiert, die Sie nicht erwartet haben.
Marc Gravell
3
@smiron Es ist sehr wahrscheinlich, dass Sie den Zufall auch außerhalb eines Schlosses verwenden. Das Sperren verhindert nicht den gesamten Zugriff auf das, was Sie sperren. Es stellt lediglich sicher, dass zwei Sperranweisungen auf derselben Instanz nicht gleichzeitig ausgeführt werden. Hilft also lock (syncObject)nur, wenn alle random.Next() Anrufe auch innerhalb sind lock (syncObject). Wenn das Szenario auch mit dem richtigen passiert das Sie beschreiben , lockNutzung, ist es auch extrem geschieht wahrscheinlich in einem Singlethread - Szenario (zB Randomwird subtil gebrochen).
Luaan
118

Zur Erleichterung der Wiederverwendung in Ihrer gesamten Anwendung kann eine statische Klasse hilfreich sein.

public static class StaticRandom
{
    private static int seed;

    private static ThreadLocal<Random> threadLocal = new ThreadLocal<Random>
        (() => new Random(Interlocked.Increment(ref seed)));

    static StaticRandom()
    {
        seed = Environment.TickCount;
    }

    public static Random Instance { get { return threadLocal.Value; } }
}

Sie können dann eine statische Zufallsinstanz mit Code wie verwenden

StaticRandom.Instance.Next(1, 100);
Phil
quelle
62

Marks Lösung kann sehr teuer sein, da sie jedes Mal synchronisiert werden muss.

Mithilfe des threadspezifischen Speichermusters können wir die Notwendigkeit einer Synchronisierung umgehen:


public class RandomNumber : IRandomNumber
{
    private static readonly Random Global = new Random();
    [ThreadStatic] private static Random _local;

    public int Next(int max)
    {
        var localBuffer = _local;
        if (localBuffer == null) 
        {
            int seed;
            lock(Global) seed = Global.Next();
            localBuffer = new Random(seed);
            _local = localBuffer;
        }
        return localBuffer.Next(max);
    }
}

Messen Sie die beiden Implementierungen und Sie sollten einen signifikanten Unterschied sehen.

Hans Malherbe
quelle
12
Schlösser sind sehr billig, wenn sie nicht angefochten werden ... und selbst wenn sie angefochten werden, würde ich erwarten, dass der Code "Jetzt etwas mit der Nummer machen" die Kosten für das Schloss in den interessantesten Szenarien in den Schatten stellt.
Marc Gravell
4
Einverstanden, dies löst das Sperrproblem, aber ist dies nicht immer noch eine sehr komplizierte Lösung für ein triviales Problem: Sie müssen zwei Codezeilen schreiben, um eine Zufallszahl anstelle einer zu generieren. Lohnt es sich wirklich, das Lesen einer einfachen Codezeile zu sparen?
EMP
4
+1 Die Verwendung einer zusätzlichen globalen RandomInstanz zum Abrufen des Startwerts ist eine gute Idee. Beachten Sie auch, dass der Code mithilfe der ThreadLocal<T>in .NET 4 eingeführten Klasse (wie Phil auch unten schrieb ) weiter vereinfacht werden kann .
Groo
40

Meine Antwort von hier :

Wiederholen Sie einfach die richtige Lösung :

namespace mySpace
{
    public static class Util
    {
        private static rnd = new Random();
        public static int GetRandom()
        {
            return rnd.Next();
        }
    }
}

So können Sie anrufen:

var i = Util.GetRandom();

alle den ganzen.

Wenn Sie unbedingt eine echte zustandslose statische Methode benötigen , um Zufallszahlen zu generieren, können Sie sich auf a verlassen Guid.

public static class Util
{
    public static int GetRandom()
    {
        return Guid.NewGuid().GetHashCode();
    }
}

Es wird ein bisschen langsamer sein, kann aber viel zufälliger sein als Random.Next, zumindest aus meiner Erfahrung.

Aber nicht :

new Random(Guid.NewGuid().GetHashCode()).Next();

Die unnötige Objekterstellung wird es insbesondere unter einer Schleife langsamer machen.

Und niemals :

new Random().Next();

Es ist nicht nur langsamer (innerhalb einer Schleife), seine Zufälligkeit ist ... nun, meiner Meinung nach nicht wirklich gut.

nawfal
quelle
10
Ich bin mit dem Fall Guid nicht einverstanden. Die Random-Klasse implementiert eine gleichmäßige Verteilung. Was in Guid nicht der Fall ist. Das Ziel von Guid ist es, eindeutig und nicht gleichmäßig verteilt zu sein (und seine Implementierung basiert meistens auf einer Hardware- / Maschineneigenschaft, die das Gegenteil von ... Zufälligkeit ist).
Askolein
4
Wenn Sie die Einheitlichkeit der Guid-Generierung nicht nachweisen können, ist es falsch, sie als zufällig zu verwenden (und der Hash wäre ein weiterer Schritt von der Einheitlichkeit entfernt). Ebenso sind Kollisionen kein Problem: Gleichmäßigkeit der Kollision ist. In Bezug auf die Guid-Generation, die nicht mehr auf Hardware ist, gehe ich zu RTFM, mein schlechtes (irgendein Hinweis?)
Askolein
5
Es gibt zwei Verständnis von "Zufall": 1. Mangel an Muster oder 2. Mangel an Muster nach einer Entwicklung, die durch eine Wahrscheinlichkeitsverteilung beschrieben wird (2 in 1 enthalten). Ihr Guid-Beispiel ist in Fall 1 korrekt, nicht in Fall 2. Im Gegenteil: RandomKlasse entspricht Fall 2 (also auch Fall 1). Sie können nur die Nutzung von ersetzen Randomdurch Ihre , Guid+Hashwenn Sie nicht in Fall 2 Fall 1 wahrscheinlich genug, um die Frage zu beantworten , und dann, Ihre Guid+HashWerke in Ordnung. Aber es ist nicht klar gesagt (ps: diese Uniform )
Askolein
2
@Askolein Nur für einige Testdaten führe ich mehrere Stapel von beiden Randomund Guid.NewGuid().GetHashCode()über Ent ( fourmilab.ch/random ) aus und beide sind ähnlich zufällig. new Random(Guid.NewGuid().GetHashCode())funktioniert genauso gut wie die Verwendung eines synchronisierten "Masters" Randomzum Generieren von Seeds für "Kinder" Random. Natürlich hängt es davon ab, wie Ihr System Guids generiert - für mein System sind sie ziemlich zufällig und für andere möglicherweise sogar krypto-zufällig sein. Windows oder MS SQL scheinen heutzutage in Ordnung zu sein. Mono und / oder Mobile können jedoch unterschiedlich sein.
Luaan
2
@EdB Wie ich bereits in Kommentaren erwähnt habe, wird Guid (eine große Anzahl) zwar eindeutig sein, GetHashCodedas Guid in .NET wird jedoch aus seiner Zeichenfolgendarstellung abgeleitet. Die Ausgabe ist für meinen Geschmack ziemlich zufällig.
Nawfal
27

Ich würde lieber die folgende Klasse verwenden, um Zufallszahlen zu generieren:

byte[] random;
System.Security.Cryptography.RNGCryptoServiceProvider prov = new System.Security.Cryptography.RNGCryptoServiceProvider();
prov.GetBytes(random);
Ferner Schrei
quelle
30
Ich bin nicht einer der Abwärtswähler, aber beachten Sie, dass Standard-PNRG einem echten Bedürfnis dienen - dh in der Lage zu sein, eine Sequenz aus einem bekannten Samen wiederholt zu reproduzieren. Manchmal sind die bloßen Kosten eines echten kryptografischen RNG zu hoch. Und manchmal ist ein Krypto-RNG notwendig. Pferde für Kurse sozusagen.
Marc Gravell
4
Laut der Dokumentation ist diese Klasse threadsicher, das ist also etwas für sie.
Rob Church
Wie groß ist die Wahrscheinlichkeit, dass zwei zufällige Zeichenfolgen damit ein und dasselbe sind? Wenn die Zeichenfolge nur aus 3 Zeichen besteht, wird dies wahrscheinlich mit hoher Wahrscheinlichkeit geschehen. Was ist jedoch mit einer Länge von 255 Zeichen möglich, wenn dieselbe zufällige Zeichenfolge verwendet wird, oder ist garantiert, dass dies mit dem Algorithmus nicht möglich ist?
Lyubomir Velchev
16

1) Versuchen Sie, wie Marc Gravell sagte, EINEN Zufallsgenerator zu verwenden. Es ist immer cool, dies dem Konstruktor hinzuzufügen: System.Environment.TickCount.

2) Ein Tipp. Angenommen, Sie möchten 100 Objekte erstellen und annehmen, dass jedes einen eigenen Zufallsgenerator haben sollte (praktisch, wenn Sie in sehr kurzer Zeit LADUNGEN von Zufallszahlen berechnen). Wenn Sie dies in einer Schleife tun würden (Generierung von 100 Objekten), könnten Sie dies folgendermaßen tun (um die vollständige Zufälligkeit sicherzustellen):

int inMyRandSeed;

for(int i=0;i<100;i++)
{
   inMyRandSeed = System.Environment.TickCount + i;
   .
   .
   .
   myNewObject = new MyNewObject(inMyRandSeed);  
   .
   .
   .
}

// Usage: Random m_rndGen = new Random(inMyRandSeed);

Prost.

Sabiland
quelle
3
Ich würde System.Environment.TickCount aus der Schleife verschieben. Wenn es während der Iteration aktiviert ist, werden zwei Elemente mit demselben Startwert initialisiert. Eine andere Möglichkeit wäre, die Anzahl der Ticks und i anders zu kombinieren (z. B. System.Environment.TickCount << 8 + i)
Dolphin
Wenn ich das richtig verstehe: Meinst du, es könnte passieren, dass "System.Environment.TickCount + i" den gleichen Wert ergibt?
Sabiland
EDIT: Natürlich muss TickCount nicht in der Schleife sein. Mein Fehler :).
Sabiland
2
Der Standardkonstruktor Random()ruft Random(Environment.TickCount)trotzdem auf
Alsty
5

Jedes Mal, wenn Sie ausführen

Random random = new Random (15);

Es spielt keine Rolle, ob Sie es millionenfach ausführen, Sie werden immer den gleichen Startwert verwenden.

Wenn du benutzt

Random random = new Random ();

Sie erhalten eine andere Zufallszahlenfolge, wenn ein Hacker den Startwert errät und Ihr Algorithmus mit der Sicherheit Ihres Systems zusammenhängt - Ihr Algorithmus ist fehlerhaft. Ich Sie führen mult. In diesem Konstruktor wird der Startwert von der Systemuhr angegeben. Wenn mehrere Instanzen in einem sehr kurzen Zeitraum (Millisekunden) erstellt werden, ist es möglich, dass sie denselben Startwert haben.

Wenn Sie sichere Zufallszahlen benötigen, müssen Sie die Klasse verwenden

System.Security.Cryptography.RNGCryptoServiceProvider

public static int Next(int min, int max)
{
    if(min >= max)
    {
        throw new ArgumentException("Min value is greater or equals than Max value.");
    }
    byte[] intBytes = new byte[4];
    using(RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        rng.GetNonZeroBytes(intBytes);
    }
    return  min +  Math.Abs(BitConverter.ToInt32(intBytes, 0)) % (max - min + 1);
}

Verwendungszweck:

int randomNumber = Next(1,100);
Joma
quelle
It does not matter if you execute it millions of times, you will always use the same seed. Das stimmt nur, wenn Sie den Samen selbst angeben.
LarsTech
Repariert. Vielen Dank Genau wie Sie sagen, LarsTech, wenn immer der gleiche Startwert angegeben wird, wird immer die gleiche Folge von Zufallszahlen generiert. In meiner Antwort verweise ich auf den Konstruktor mit Parametern, wenn Sie immer den gleichen Startwert verwenden. Die Random-Klasse generiert nur Pseudozufallszahlen. Wenn jemand herausfindet, welchen Startwert Sie in Ihrem Algorithmus verwendet haben, kann dies die Sicherheit oder Zufälligkeit Ihres Algorithmus beeinträchtigen. Mit der RNGCryptoServiceProvider-Klasse können Sie sicher Zufallszahlen haben. Ich habe bereits korrigiert, vielen Dank für die Korrektur.
Joma
0

Deklarieren Sie einfach die Random-Klassenvariable wie folgt:

    Random r = new Random();
    // ... Get three random numbers.
    //     Here you'll get numbers from 5 to 9
    Console.WriteLine(r.Next(5, 10));

Wenn Sie jedes Mal eine andere Zufallszahl aus Ihrer Liste erhalten möchten, verwenden Sie

r.Next(StartPoint,EndPoint) //Here end point will not be included

Jedes Mal durch Random r = new Random()einmalige Deklaration .

Abdul
quelle
Wenn Sie aufrufen new Random(), wird die Systemuhr verwendet. Wenn Sie jedoch den gesamten Code zweimal hintereinander aufrufen, bevor sich die Uhr ändert, erhalten Sie dieselbe Zufallszahl. Das ist der springende Punkt der obigen Antworten.
Savage
-1

Es gibt viele Lösungen, hier eine: Wenn Sie nur Zahlen möchten, löschen Sie die Buchstaben und die Methode erhält eine zufällige und die Ergebnislänge.

public String GenerateRandom(Random oRandom, int iLongitudPin)
{
    String sCharacters = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
    int iLength = sCharacters.Length;
    char cCharacter;
    int iLongitudNuevaCadena = iLongitudPin; 
    String sRandomResult = "";
    for (int i = 0; i < iLongitudNuevaCadena; i++)
    {
        cCharacter = sCharacters[oRandom.Next(iLength)];
        sRandomResult += cCharacter.ToString();
    }
    return (sRandomResult);
}
Marztres
quelle
Das Grundproblem ist immer noch dasselbe: Sie übergeben eine RandomInstanz, erwarten jedoch weiterhin, dass der Aufrufer eine gemeinsam genutzte Instanz erstellt. Wenn der Aufrufer jedes Mal eine neue Instanz erstellt und der Code zweimal ausgeführt wird, bevor sich die Uhr ändert, erhalten Sie dieselbe Zufallszahl. Diese Antwort macht also immer noch Annahmen, die falsch sein könnten.
Savage
Der springende Punkt bei der Erstellung einer Methode zum Generieren von Zufallszahlen ist die Kapselung - die aufrufende Methode muss sich nicht um die Implementierung kümmern, sondern ist nur daran interessiert, eine Zufallszahl zurückzubekommen
Savage