Generator liczb losowych generujący tylko jedną liczbę losową


765

Mam następującą funkcję:

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

Jak to nazywam:

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

Jeśli uruchomię tę pętlę za pomocą debugera podczas uruchamiania, otrzymam różne wartości (to jest to, czego chcę). Jeśli jednak umieszczę punkt przerwania dwie linie poniżej tego kodu, wszyscy członkowie mactablicy mają równą wartość.

Dlaczego tak się dzieje?


20
użycie new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);nie daje lepszych „losowych” liczb niż.Next(0, 256)
bohdan_trotsenko

Może ci się przydać ten pakiet NuGet . Zapewnia Rand.Next(int, int)metodę statyczną , która zapewnia statyczny dostęp do losowych wartości bez blokowania lub problemów z ponownym użyciem materiału siewnego
ChaseMedallion

Odpowiedzi:


1042

Za każdym razem new Random()jest to inicjowane przy użyciu zegara. Oznacza to, że w ciasnej pętli dostajesz tę samą wartość wiele razy. Powinieneś zachować pojedyncze Losowe wystąpienie i dalej używać Dalej w tym samym wystąpieniu.

//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);
    }
}

Edytuj (patrz komentarze): dlaczego potrzebujemy locktutaj?

Zasadniczo Nextzamierza zmienić wewnętrzny stan Randominstancji. Jeśli zrobimy to w tym samym czasie z wielu wątków, to mógłby argumentować „my właśnie sprawiło, że jeszcze bardziej przypadkowy wynik”, ale co my właściwie robi potencjalnie zerwania wdrożenie wewnętrznego, a także moglibyśmy zacząć uzyskiwać te same numery z różnych wątków, co może być problemem - a może nie. Większe znaczenie ma jednak gwarancja tego, co dzieje się wewnętrznie; ponieważ Randomnie daje żadnych gwarancji bezpieczeństwa nici. Istnieją zatem dwa ważne podejścia:

  • Zsynchronizuj, abyśmy nie mieli do niego dostępu w tym samym czasie z różnych wątków
  • Używaj różnych Randominstancji na wątek

Oba mogą być w porządku; ale muteksowanie pojedynczej instancji od wielu rozmówców w tym samym czasie to po prostu prośba o kłopoty.

lockOsiąga pierwszy (i prostsze) tych metod; jednak innym podejściem może być:

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

jest to następnie dla wątku, więc nie trzeba synchronizować.


19
Zasadniczo wszystkie metody statyczne powinny być bezpieczne dla wątków, ponieważ trudno jest zagwarantować, że wiele wątków nie wywoła go w tym samym czasie. Zazwyczaj nie jest konieczne, aby instancyjne (tj. Niestatyczne) metody były bezpieczne dla wątków.
Marc Gravell

5
@Florin - nie ma różnicy między „stosem” między nimi. Pola statyczne są tak samo „stanem zewnętrznym” i absolutnie będą dzielone między dzwoniącymi. W przypadku instancji istnieje duża szansa, że ​​różne wątki mają różne instancje (wspólny wzór). W przypadku statyki gwarantuje się , że wszystkie one dzielą się (nie włączając [ThreadStatic]).
Marc Gravell

2
@gdoron otrzymujesz błąd? „Blokada” powinna zapobiegać potykaniu się nici tutaj ...
Marc Gravell

6
@ Dan, jeśli obiekt nigdy nie jest publicznie dostępny: możesz. (Bardzo teoretyczne) ryzyko polega na tym, że blokuje się na nim inny wątek w sposób, którego się nie spodziewałeś.
Marc Gravell

3
@smiron Jest bardzo prawdopodobne, że po prostu używasz losowego elementu poza zamkiem. Blokowanie nie uniemożliwia wszystkim dostępu do tego, co blokujesz - upewnia się tylko, że dwie instrukcje blokowania w tej samej instancji nie będą działać jednocześnie. Więc lock (syncObject)pomoże tylko wtedy, gdy wszystkie random.Next() połączenia są również wewnątrz lock (syncObject). Jeśli opisany przez Ciebie scenariusz ma miejsce nawet przy prawidłowym lockużytkowaniu, niezwykle prawdopodobne jest również w scenariuszu jednowątkowym (np. RandomJest subtelnie zepsuty).
Luaan

118

Aby ułatwić ponowne użycie w całej aplikacji, może pomóc klasa statyczna.

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; } }
}

Następnie można użyć statycznej losowej instancji z kodem takim jak

StaticRandom.Instance.Next(1, 100);

62

Rozwiązanie Marka może być dość drogie, ponieważ wymaga synchronizacji za każdym razem.

Możemy ominąć potrzebę synchronizacji, stosując wzorzec przechowywania specyficzny dla wątku:


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);
    }
}

Zmierz obie implementacje i powinieneś zobaczyć znaczącą różnicę.


12
Zamki są bardzo tanie, jeśli nie są kwestionowane ... i nawet jeśli są kwestionowane, spodziewałbym się, że kod „teraz zrób coś z numerem” będzie przewyższał koszt zamka w najciekawszych scenariuszach.
Marc Gravell

4
Zgadzam się, to rozwiązuje problem blokowania, ale czy nie jest to nadal bardzo skomplikowane rozwiązanie trywialnego problemu: że musisz napisać „dwie” linie kodu, aby wygenerować losową liczbę zamiast jednej. Czy naprawdę warto oszczędzać na czytaniu jednego prostego wiersza kodu?
EMP

4
+1 Korzystanie z dodatkowej globalnej Randominstancji w celu uzyskania nasion jest dobrym pomysłem. Zauważ też, że kod można dodatkowo uprościć za pomocą ThreadLocal<T>klasy wprowadzonej w .NET 4 (jak Phil również napisał poniżej ).
Groo

40

Moja odpowiedź stąd :

Powtarzam tylko właściwe rozwiązanie :

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

Możesz więc zadzwonić:

var i = Util.GetRandom();

wszystko poprzez.

Jeśli do generowania liczb losowych potrzebujesz bezwzględnej metody statycznej bezstanowej , możesz polegać na Guid.

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

To będzie odrobinę wolniejsze, ale może być znacznie bardziej przypadkowy niż Random.Next, przynajmniej z mojego doświadczenia.

Ale nie :

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

Niepotrzebne tworzenie obiektów spowoduje spowolnienie, szczególnie w pętli.

I nigdy :

new Random().Next();

Nie tylko jest wolniejszy (wewnątrz pętli), ale jego losowość jest ... cóż, według mnie niezbyt dobra ...


10
Nie zgadzam się ze sprawą Guid. Klasa Random implementuje jednolity rozkład. Tak nie jest w przypadku Guida. Guid ma być unikalny, nierównomiernie rozłożony (a jego implementacja jest w większości przypadków oparta na pewnych właściwościach sprzętowych / maszynowych, co jest przeciwieństwem ... losowości).
Askolein

4
jeśli nie możesz udowodnić jednorodności generacji Guida, niewłaściwe jest używanie go losowo (a Hash byłby kolejnym krokiem od jednolitości). Podobnie, kolizje nie stanowią problemu: jednolita jest kolizja. Jeśli chodzi o generację Guida, która nie jest już na sprzęcie, idę do RTFM, mój zły (jakieś referencje?)
Askolein

5
Istnieją dwa pojęcia „losowe”: 1. brak wzorca lub 2. brak wzorca po ewolucji opisanej rozkładem prawdopodobieństwa (2 zawarte w 1). Twój przykład Guid jest poprawny w przypadku 1, a nie w przypadku 2. Przeciwnie: Randomklasa pasuje do przypadku 2 (a więc również przypadku 1). Można wymienić tylko wykorzystanie Randomprzez twoją Guid+Hashjeśli nie w przypadku 2. Przypadek 1 to chyba wystarczy, aby odpowiedzieć na pytanie, a następnie swoją Guid+Hashkarę działa. Ale nie jest wyraźnie powiedziane (ps: ten mundur )
Askolein

2
@Askolein Dla niektórych danych testowych uruchamiam kilka partii obu Randomi Guid.NewGuid().GetHashCode()poprzez Ent ( fourmilab.ch/random ) i oba są podobnie losowe. new Random(Guid.NewGuid().GetHashCode())działa równie dobrze, jak przy użyciu zsynchronizowanego „mistrza” Randomdo generowania nasion dla „dzieci” Random. Oczywiście zależy to od tego, jak twój system generuje Guids - dla mojego systemu są one dość losowe, a od innych może to być być nawet krypto-losowym. Tak więc Windows lub MS SQL wydaje się dziś w porządku. Mono i / lub telefony komórkowe mogą być jednak inne.
Luaan

2
@EdB Jak powiedziałem wcześniej w komentarzach, podczas gdy Guid (duża liczba) ma być unikalny, GetHashCodeGuid w .NET pochodzi z jego reprezentacji łańcuchowej. Jak dla mnie, wynik jest dość losowy.
nawfal

27

Wolę użyć następującej klasy do generowania liczb losowych:

byte[] random;
System.Security.Cryptography.RNGCryptoServiceProvider prov = new System.Security.Cryptography.RNGCryptoServiceProvider();
prov.GetBytes(random);

30
Nie jestem zwolennikiem głosowania w dół, ale zauważam, że standardowe PNRG naprawdę potrzebują - tj. Aby móc powtarzalnie odtwarzać sekwencję ze znanego materiału siewnego. Czasami zwykły koszt prawdziwego kryptograficznego RNG jest zbyt wysoki. A czasami konieczne jest szyfrowanie RNG. Konie na kursy, że tak powiem.
Marc Gravell

4
Zgodnie z dokumentacją ta klasa jest bezpieczna dla wątków, więc jest to coś na jej korzyść.
Rob Church

Jakie jest prawdopodobieństwo, że dwa losowe ciągi znaków będą jednym i tym samym, używając tego? Jeśli ciąg ma tylko 3 znaki, myślę, że stanie się to z dużym prawdopodobieństwem, ale co, jeśli długość 255 znaków jest możliwa, aby mieć taki sam losowy ciąg znaków lub jest zagwarantowane, że nie może się to zdarzyć z algorytmu?
Lyubomir Velchev

16

1) Jak powiedział Marc Gravell, spróbuj użyć JEDNEGO generatora losowego. Zawsze fajnie jest dodać to do konstruktora: System.Environment.TickCount.

2) Jedna wskazówka. Załóżmy, że chcesz utworzyć 100 obiektów i załóżmy, że każdy z nich powinien mieć własny generator losowy (przydatny, jeśli obliczasz ŁADUNKI liczb losowych w bardzo krótkim czasie). Jeśli zrobiłbyś to w pętli (generowanie 100 obiektów), możesz to zrobić w ten sposób (aby zapewnić pełną losowość):

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);

Twoje zdrowie.


3
Chciałbym przenieść System.Environment.TickCount poza pętlę. Jeśli zaznacza się podczas iteracji, będziesz mieć dwa przedmioty zainicjowane dla tego samego ziarna. Inną opcją byłoby połączenie licznika i w inny sposób (np. System.Environment.TickCount << 8 + i)
Dolphin

Jeśli dobrze rozumiem: czy masz na myśli, że może się zdarzyć, że „System.Environment.TickCount + i” może spowodować taką samą wartość?
sabiland

EDYCJA: Oczywiście nie trzeba mieć TickCount wewnątrz pętli. Mój błąd :).
sabiland

2
Domyślny Random()konstruktor i Random(Environment.TickCount)tak wywołuje
Alsty

5

Za każdym razem, gdy wykonujesz

Random random = new Random (15);

Nie ma znaczenia, czy wykonasz to miliony razy, zawsze użyjesz tego samego ziarna.

Jeśli użyjesz

Random random = new Random ();

Otrzymujesz inną sekwencję liczb losowych, jeśli haker zgadnie ziarno, a twój algorytm jest związany z bezpieczeństwem twojego systemu - twój algorytm jest zepsuty. Ja wykonujesz mult. W tym konstruktorze ziarno jest określane przez zegar systemowy i jeśli w bardzo krótkim czasie (milisekundach) utworzonych zostanie kilka instancji, możliwe jest, że mogą mieć to samo ziarno.

Jeśli potrzebujesz bezpiecznych liczb losowych, musisz skorzystać z klasy

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);
}

Stosowanie:

int randomNumber = Next(1,100);

It does not matter if you execute it millions of times, you will always use the same seed. To nie jest prawda, chyba że sam określisz ziarno.
LarsTech,

Naprawione. Dzięki Dokładnie tak, jak mówisz LarsTech, jeśli zawsze podano to samo ziarno, zawsze generowana będzie ta sama sekwencja liczb losowych. W mojej odpowiedzi odnoszę się do konstruktora z parametrami, jeśli zawsze używasz tego samego ziarna. Klasa Random generuje tylko liczby pseudolosowe. Jeśli ktoś dowie się, jakie ziarno zostało użyte w algorytmie, może to zagrozić bezpieczeństwu lub losowości algorytmu. Dzięki klasie RNGCryptoServiceProvider możesz bezpiecznie mieć liczby losowe. Już poprawiłem, dziękuję bardzo za korektę.
Joma,

0

po prostu zadeklaruj zmienną klasy Losowej w następujący sposób:

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

jeśli chcesz uzyskać inną liczbę losową za każdym razem z listy, użyj

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

Za każdym razem, deklarując Random r = new Random()raz.


Kiedy dzwonisz new Random(), używa zegara systemowego, ale jeśli wywołasz ten cały kod dwa razy z rzędu przed zmianą zegara, otrzymasz ten sam losowy numer. To jest sedno powyższych odpowiedzi.
Savage

-1

Istnieje wiele rozwiązań, tutaj jedno: jeśli chcesz tylko liczby, usuń litery, a metoda otrzyma losową i wynikową długość.

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);
}

Podstawowy problem pozostaje ten sam - przekazujesz w Randominstancji, ale nadal oczekujesz, że osoba dzwoniąca utworzy instancję udostępnioną. Jeśli dzwoniący tworzy nowe wystąpienie za każdym razem, a kod jest wykonywany dwukrotnie przed zmianą zegara, otrzymasz ten sam losowy numer. Tak więc ta odpowiedź wciąż przyjmuje założenia, które mogą być błędne.
Savage

Ponadto sednem posiadania metody generowania liczb losowych jest hermetyzacja - że metoda wywołująca nie musi martwić się o implementację, jest zainteresowana tylko odzyskaniem losowej liczby
Savage
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.