Генератор случайных чисел, генерирующий только одно случайное число


766

У меня есть следующая функция:

//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массива будут иметь одинаковое значение.

Почему это происходит?


21
использование new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);не дает лучших «случайных» чисел, чем.Next(0, 256)
bohdan_trotsenko

1
Вы можете найти этот пакет NuGet полезным. Он предоставляет статический Rand.Next(int, int)метод, который обеспечивает статический доступ к случайным значениям, не блокируя и не сталкиваясь с проблемой повторного использования
начального числа

Ответы:


1043

Каждый раз, когда вы делаете new Random()это, инициализируется с помощью часов. Это означает, что в тесном цикле вы получаете одно и то же значение много раз. Вы должны сохранить один экземпляр 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]).
Марк Гравелл

2
@gdoron у тебя ошибка? «Блокировка» должна препятствовать тому, чтобы потоки пересекались здесь друг с другом ...
Марк Гравелл

6
@ Дан, если объект никогда не выставляется на всеобщее обозрение: вы можете. (Очень теоретический) риск заключается в том, что какой-то другой поток блокирует его так, как вы этого не ожидали.
Марк Гравелл

3
@smiron Весьма вероятно, что вы просто используете случайные числа вне замка. Блокировка не запрещает весь доступ к тому, что вы блокируете - она ​​просто гарантирует, что два оператора блокировки в одном экземпляре не будут выполняться одновременно. Так lock (syncObject)поможет только если все random.Next() звонки тоже внутри lock (syncObject). Если описанный вами сценарий действительно происходит даже при правильном lockиспользовании, он также очень вероятно случается в однопоточном сценарии (например Random, слегка нарушается).
Luaan

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
Блокировки очень дешевы, когда они не оспариваются ... и даже если бы они оспаривались, я ожидал бы, что код "теперь сделай что-нибудь с номером" уменьшит стоимость блокировки в большинстве интересных сценариев.
Марк Гравелл

4
Согласен, это решает проблему блокировки, но разве это не очень сложное решение тривиальной проблемы: вам нужно написать «две» строки кода, чтобы сгенерировать случайное число вместо одного. Это действительно стоит того, чтобы сохранить чтение одной простой строки кода?
EMP

4
+1 Использование дополнительного глобального Randomэкземпляра для получения семени - хорошая идея. Также обратите внимание, что код можно еще более упростить, используя ThreadLocal<T>класс, представленный в .NET 4 (как Фил также написал ниже ).
Groo

40

Мой ответ отсюда :

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

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

Так что вы можете позвонить:

var i = Util.GetRandom();

все на протяжении

Если вам строго необходим статический метод без сохранения состояния для генерации случайных чисел, вы можете положиться на 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 реализует равномерное распределение. Что не так в Guid. Цель Guid - быть уникальным, а не распределенным равномерно (и его реализация в большинстве случаев основана на некотором свойстве оборудования / машины, которое противоположно ... случайности).
Асколейн

4
если вы не можете доказать однородность генерации Guid, то неправильно использовать его как случайный (а хеш будет еще одним шагом от однородности). Кроме того, столкновения не проблема: однородность столкновения. Относительно того, что поколение Guid больше не работает на оборудовании, я собираюсь перейти на RTFM, мой плохой (какая-либо ссылка?)
Askolein

5
Существует два понимания «случайного»: 1. отсутствие шаблона или 2. отсутствие шаблона после эволюции, описываемой распределением вероятностей (2 включены в 1). Ваш пример Guid верен в случае 1, а не в случае 2. Напротив: Randomкласс соответствует случаю 2 (таким образом, случай 1 тоже). Вы можете заменить использование Randomтолько вашим, Guid+Hashесли вы не в случае 2. Случай 1, вероятно, достаточно, чтобы ответить на вопрос, а затем, все Guid+Hashработает нормально. Но это не ясно сказано (пс: это униформа )
Асколейн

2
@Askolein Просто для некоторых тестовых данных, я запускаю несколько пакетов Randomи Guid.NewGuid().GetHashCode()через Ent ( fourmilab.ch/random ), и оба они одинаково случайны. new Random(Guid.NewGuid().GetHashCode())работает так же хорошо, как и использование синхронизированного «мастера» Randomдля генерации Randomначальных чисел для «дочерних» объектов. Конечно, это зависит от того, как ваша система генерирует Guids - для моей системы они довольно случайны, а для других это может даже быть крипто-случайным. Итак, Windows или MS SQL в настоящее время выглядят хорошо. Моно и / или мобильный может отличаться.
Luaan

2
@EdB Как я уже говорил в комментариях ранее, хотя Guid (большое число) должен быть уникальным, GetHashCodeGuid в .NET является производным от его строкового представления. Вывод на мой взгляд довольно случайный.
nawfal

27

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

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

30
Я не один из тех, кто отрицает, но обратите внимание, что стандартные PNRG действительно служат истинной необходимости - т.е. иметь возможность повторного воспроизведения последовательности из известного семени. Иногда чистая стоимость настоящего криптографического ГСЧ слишком велика. И иногда необходим крипто-ГСЧ. Лошади на курсы, так сказать.
Марк Гравелл

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 из цикла. Если во время итерации это произойдет, то у вас будет два объекта, инициализированных одним и тем же начальным числом. Другой вариант - объединить тик-счет a i по-другому (например, System.Environment.TickCount << 8 + i)
Dolphin

Если я правильно понимаю: вы имеете в виду, что может случиться так, что «System.Environment.TickCount + i» может привести к ОДНОМУ ТОЛЬКО значению?
Сабиленд

РЕДАКТИРОВАТЬ: Конечно, нет необходимости иметь TickCount внутри цикла. Виноват :).
Сабиленд

2
В любом случае Random()конструктор по умолчанию вызываетRandom(Environment.TickCount)
Alsty

5

Каждый раз, когда вы выполняете

Random random = new Random (15);

Неважно, если вы выполните его миллионы раз, вы всегда будете использовать одно и то же семя.

Если вы используете

Random random = new Random ();

Вы получаете другую последовательность случайных чисел, если хакер угадает семя, и ваш алгоритм связан с безопасностью вашей системы - ваш алгоритм нарушен. Я тебя выполню мульт. В этом конструкторе начальное число определяется системными часами, и, если несколько экземпляров создаются за очень короткий период времени (миллисекунды), возможно, что они могут иметь одинаковое начальное число.

Если вам нужны безопасные случайные числа, вы должны использовать класс

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 вы можете безопасно иметь случайные числа. Я уже исправил, большое спасибо за исправление.
Joma

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()он использует системные часы, но если вы вызываете весь этот код дважды подряд, прежде чем часы изменятся, вы получите одно и то же случайное число. Вот и весь смысл ответов выше.
Дикарь

-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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.