Генератор випадкових чисел генерує лише одне випадкове число


765

У мене є така функція:

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

Як я це називаю:

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

Якщо я переходжу цей цикл з налагоджувачем під час виконання, я отримую різні значення (що я хочу). Однак якщо я поставив точку розриву на два рядки нижче цього коду, всі члени macмасиву мають однакове значення.

Чому це відбувається?


20
використання new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);не дає кращих "випадкових" чисел, ніж.Next(0, 256)
bohdan_trotsenko

Цей пакет NuGet може бути корисним. Він надає статичний Rand.Next(int, int)метод, який забезпечує статичний доступ до випадкових значень, не блокуючи або не
стикаючись із

Відповіді:


1042

Кожен раз, коли ви new Random()це робите , ініціалізується за допомогою годинника. Це означає, що в тісному циклі ви отримуєте однакове значення багато разів. Ви повинні зберігати один випадковий екземпляр і продовжувати використовувати Next у тому ж екземплярі.

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

Редагувати (див. Коментарі): навіщо нам lockтут потрібен ?

В основному, Nextзбирається змінити внутрішній стан Randomекземпляра. Якщо ми будемо робити це одночасно з декількох потоків, ви можете стверджувати, що "ми просто зробили результат ще більш випадковим", але те, що ми насправді робимо, - це потенційно порушує внутрішню реалізацію, і ми також можемо почати отримувати однакові цифри з різних ниток, що може бути проблемою - а може й не бути. Однак, більша проблема - гарантія того, що відбувається всередині; так як Randomце НЕ робить ніяких гарантій безпеки потоків. Таким чином, існує два дійсних підходи:

  • Синхронізуйте, щоб ми не мали доступу до нього одночасно з різних потоків
  • Використовуйте різні Randomекземпляри на нитку

Або може бути добре; але зміна одного екземпляра з кількох абонентів одночасно - це просто клопотання.

lockДосягає перші (і простіше) ці підходи; однак, може бути інший підхід:

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

це тоді за ниткою, тому вам не потрібно синхронізувати.


19
Як правило, всі статичні методи повинні бути безпечними для потоків, оскільки важко гарантувати, що кілька потоків не викличуть його одночасно. Зазвичай не потрібно робити екземплярні (тобто нестатичні) методи потокобезпечними.
Марк Гравелл

5
@Florin - різниці між повторним "стеком" між ними немає. Статичні поля є настільки ж «зовнішніми станами», і вони будуть абсолютно розділені між абонентами. З примірниками є хороший шанс, що різні потоки мають різні екземпляри (загальний шаблон). За допомогою статики гарантується, що всі вони діляться (не включаючи [ThreadStatic]).
Marc Gravell

2
@gdoron Ви отримуєте помилку? "Замок" повинен запобігати, щоб нитки не перетиналися один на одного тут ...
Marc Gravell

6
@Dan, якщо об’єкт ніколи не відкривається публічно: ви можете. Ризик (дуже теоретичний) полягає в тому, що якась інша нитка замикається на ньому так, як ви не очікували.
Марк Гравелл

3
@smiron Це дуже ймовірно, що ви просто використовуєте випадковий і поза замком. Блокування не перешкоджає всьому доступу до того, що ви замикаєте - він просто гарантує, що два заяви про блокування в одному і тому ж екземплярі не будуть працювати одночасно. Тож lock (syncObject)допоможе лише в тому випадку, якщо всі random.Next() дзвінки також знаходяться в межах lock (syncObject). Якщо описаний вами сценарій трапляється навіть при правильному lockвикористанні, він також надзвичайно вірогідний у однопотоковому сценарії (наприклад Random, тонко порушений).
Луань

118

Для зручності повторного використання у вашій програмі може допомогти статичний клас.

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

Потім можна використовувати статичний випадковий екземпляр з таким кодом, як

StaticRandom.Instance.Next(1, 100);

62

Рішення Марка може бути досить дорогим, оскільки його потрібно синхронізувати щоразу.

Ми можемо обійти необхідність синхронізації, використовуючи специфічний для потоку шаблон зберігання:


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

Виміряйте дві реалізації та ви побачите значну різницю.


12
Замки дуже дешеві, коли їх не оскаржують ... і навіть якщо вони оскаржуються, я б очікував, що код «зараз зроби щось з номером» дозволить уникнути вартості блокування в найбільш цікавих сценаріях.
Marc Gravell

4
Погоджено, це вирішує проблему блокування, але чи не є це все ще дуже складним рішенням тривіальної проблеми: що вам потрібно написати '' два '' рядки коду, щоб генерувати випадкове число замість одного. Чи справді цього варто, щоб заощадити при читанні одного простого рядка коду?
EMP

4
+1 Використання додаткового глобального Randomпримірника для отримання насіння - хороша ідея. Зауважте також, що код можна додатково спростити за допомогою ThreadLocal<T>класу, введеного в .NET 4 (як Філ також писав нижче ).
Гроо

40

Моя відповідь звідси :

Просто повторюючи правильне рішення :

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

Тож ви можете зателефонувати:

var i = Util.GetRandom();

всі по всьому.

Якщо вам точно потрібен справжній статичний метод без громадянства для генерування випадкових чисел, ви можете покластися на a Guid.

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

Це буде трохи пізніше, але може бути набагато більш випадковим, ніж Random.Next, принаймні, з мого досвіду.

Але не :

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

Потрібне створення об'єкта зробить його більш повільним, особливо під циклом.

І ніколи :

new Random().Next();

Мало того, що він повільніше (всередині циклу), його випадковість ... ну, не дуже добре, на мене.


10
Я не згоден зі справою Гід. Клас Random реалізує рівномірний розподіл. Що не стосується Гуїда. Основна мета полягає в тому, щоб бути унікальним не рівномірно розподіленим (і його реалізація проводиться більшу частину часу на основі деякої апаратної / машинної властивості, яка протилежна ... випадковості).
Асколейн

4
якщо ви не можете довести рівномірність покоління Guid, то неправильно використовувати його як випадкове (а хеш був би ще одним кроком від рівномірності). Так само зіткнення не є проблемою: рівномірність зіткнення є. Щодо того, що покоління Guid більше не працює на апараті, я
переходжу

5
Існує два розуміння поняття "Випадкове": 1. відсутність шаблону або 2. відсутність шаблону після еволюції, описаної розподілом ймовірностей (2 включені в 1). Ваш приклад Правил правильний у випадку 1, а не у випадку 2. Навпаки: Randomклас відповідає випадку 2 (таким чином, і випадку 1). Ви можете замінити використання Randomвашим Guid+Hashлише у випадку, якщо у вас немає справи. Випаду 1, ймовірно, достатньо, щоб відповісти на запитання, а значить, ваша Guid+Hashробота прекрасна. Але це чітко не сказано (ps: ця уніформа )
Асколейн

2
@Askolein Просто для деяких тестових даних я запускаю кілька партій як через Ent, так Randomі Guid.NewGuid().GetHashCode()через Ent ( fourmilab.ch/random ), і обидві подібні випадково. new Random(Guid.NewGuid().GetHashCode())працює так само, як і використання синхронізованого "майстра" Randomдля генерації насіння для "дітей" Random. Звичайно, це залежить від того, як ваша система створює керівні принципи - для моєї системи вони є досить випадковими, а для інших - це навіть бути криптовалютними. Тож Windows чи MS SQL сьогодні здаються прекрасними. Моно та / або мобільні можуть бути різними.
Луань

2
@EdB Як я вже говорив у коментарях раніше, хоча Guid (велика кількість) має бути унікальним, керівництво GetHashCodeв .NET походить від його рядкового представлення. Вихід цілком випадковий на мій смак.
nawfal

27

Я вважаю за краще використовувати наступний клас для генерування випадкових чисел:

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

30
Я не один з тих, хто не має права голосу, але зауважую, що стандартний PNRG справді потребує, тобто вміти повторно відтворювати послідовність із відомого насіння. Іноді велика вартість справжнього криптографічного RNG занадто велика. І іноді необхідна криптовалюта. Коні на курси, так би мовити
Марк Гравелл

4
Згідно з документацією, цей клас є безпечним для потоків, тому це щось на користь.
Церква Роб

Яка ймовірність двох випадкових рядків бути одним і тим же, використовуючи це? Якщо в рядку є лише 3 символи, я думаю, це станеться з великою часткою ймовірності, але що робити, якщо довжина 255 символів може бути однаковою рядком або гарантується, що це не може відбутися за допомогою алгоритму?
Любомир Велчев

16

1) Як сказав Марк Гравелл, спробуйте використовувати ОДИН випадковий генератор. Завжди здорово додавати це до конструктора: System.Environment.TickCount.

2) Одна порада. Скажімо, ви хочете створити 100 об'єктів і припустимо, що кожен з них повинен мати власний генератор випадкових випадків (зручно, якщо ви обчислюєте ЗАВАНТАЖЕННЯ випадкових чисел за дуже короткий проміжок часу). Якщо ви зробите це в циклі (генерація 100 об'єктів), ви можете зробити це так (щоб забезпечити повну випадковість):

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

Ура.


3
Я переміщу System.Environment.TickCount з циклу. Якщо вона відмічається під час ітерації, у вас буде два ініціалізовані елементи на одне насіння. Іншим варіантом буде поєднувати галочку i i по-різному (наприклад, System.Environment.TickCount << 8 + i)
Дельфін

Якщо я правильно розумію: ти маєш на увазі, що може статися так, що "System.Environment.TickCount + i" може призвести до значення SAME?
sabiland

EDIT: Звичайно, не потрібно мати TickCount всередині циклу. Моє ліжко :).
sabiland

2
Конструктор за замовчуванням все одно Random()дзвонитьRandom(Environment.TickCount)
Alsty

5

Кожен раз, коли ви виконуєте

Random random = new Random (15);

Не має значення, чи виконаєте ви це мільйони разів, ви завжди будете використовувати одне і те ж насіння.

Якщо ви використовуєте

Random random = new Random ();

Ви отримуєте різну послідовність випадкових чисел, якщо хакер здогадається про насіння, а ваш алгоритм пов’язаний із безпекою вашої системи - ваш алгоритм порушений. I ти виконуєш мульти. У цьому конструкторі насіння задається системним годинником, і якщо кілька екземплярів буде створено за дуже короткий проміжок часу (мілісекунд), можливо, вони можуть мати одне насіння.

Якщо вам потрібні безпечні випадкові числа, ви повинні використовувати клас

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

Використання:

int randomNumber = Next(1,100);

It does not matter if you execute it millions of times, you will always use the same seed. Це неправда, якщо ви самі не вказали насіння.
LarsTech

Виправлено. Дякую Точно, як ви кажете LarsTech, якщо завжди вказано одне і те ж насіння, завжди буде генерована однакова послідовність випадкових чисел. У своїй відповіді я посилаюся на конструктор з параметрами, якщо ви завжди використовуєте одне і те ж насіння. Клас Random генерує лише псевдовипадкові числа. Якщо хтось дізнається, яке насіння ви використовували у своєму алгоритмі, це може поставити під загрозу безпеку або випадковість вашого алгоритму. З класом RNGCryptoServiceProvider ви можете сміливо мати випадкові числа. Я вже виправився, дуже дякую за виправлення.
Джома

0

просто оголосимо змінну класу Random так:

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

якщо ви хочете отримувати різні випадкові числа кожного разу зі свого списку, тоді використовуйте

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

Кожен раз, оголошуючи Random r = new Random()один раз.


Коли ви телефонуєте, new Random()він використовує системний годинник, але якщо ви зателефонуєте цьому коду два рази поспіль, перш ніж годинник зміниться, ви отримаєте однакове випадкове число. У цьому і полягає вся суть відповідей вище.
Savage

-1

Рішень існує безліч, ось одне: якщо ви хочете лише стерти цифри, викресліть букви, і метод отримує випадкову і довжину результату.

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

Основна проблема залишається тією ж - ви передаєте Randomекземпляр, але ви все ще очікуєте, що абонент створить спільний екземпляр. Якщо абонент щоразу створює новий екземпляр і код виконується двічі до зміни годин, ви отримаєте однакове випадкове число. Тож ця відповідь все ж робить припущення, які можуть бути помилковими.
Savage

Крім того, вся суть у тому, щоб створити метод генерування випадкових чисел, полягає в інкапсуляції - що метод виклику не повинен турбуватися про реалізацію, його цікавить лише повернення випадкового числа
Savage
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.