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

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

//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.04.2009 12:11:24
использование new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);не дает лучших «случайных» чисел, чем.Next(0, 256)
bohdan_trotsenko 18.03.2016 13:16:35
Вы можете найти этот пакет NuGet полезным. Он предоставляет статический Rand.Next(int, int)метод, который обеспечивает статический доступ к случайным значениям, не блокируя и не сталкиваясь с проблемой повторного использования
ChaseMedallion 7.05.2016 16:09:02
9 ОТВЕТОВ
РЕШЕНИЕ

Каждый раз, когда вы делаете 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());

тогда это для каждого потока, поэтому вам не нужно синхронизировать.

1036
24.04.2020 01:20:39
Как правило, все статические методы должны быть поточно-ориентированными, поскольку трудно гарантировать, что несколько потоков не будут вызывать их одновременно. Обычно нет необходимости делать экземпляры (то есть нестатические) методы поточно-ориентированными.
Marc Gravell♦ 20.04.2009 12:32:22
@Florin - нет никакой разницы между двумя стеками. Статические поля являются таким же «внешним состоянием» и будут абсолютно разделены между вызывающими. В случае экземпляров существует высокая вероятность того, что разные потоки имеют разные экземпляры (общий шаблон). С помощью статики гарантируется , что все они совместно используют (не включая [ThreadStatic]).
Marc Gravell♦ 20.04.2009 13:03:49
@gdoron у тебя ошибка? «Блокировка» должна препятствовать тому, чтобы потоки пересекались здесь друг с другом ...
Marc Gravell♦ 12.03.2012 23:02:50
@ Дан, если объект никогда не выставляется на всеобщее обозрение: вы можете. (Очень теоретический) риск заключается в том, что какой-то другой поток блокирует его так, как вы этого не ожидали.
Marc Gravell♦ 31.01.2014 22:34:25
@smiron Весьма вероятно, что вы просто используете случайные данные вне замка. Блокировка не запрещает весь доступ к тому, что вы блокируете, она просто гарантирует, что два оператора блокировки в одном экземпляре не будут выполняться одновременно. Так lock (syncObject)что только поможет, если все random.Next() звонки тоже внутри lock (syncObject). Если описываемый вами сценарий действительно происходит даже при правильном lockиспользовании, он также весьма вероятен в однопоточном сценарии (например Random, слегка нарушен).
Luaan 21.04.2015 12:17:04

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

byte[] random;
System.Security.Cryptography.RNGCryptoServiceProvider prov = new System.Security.Cryptography.RNGCryptoServiceProvider();
prov.GetBytes(random);
27
22.10.2012 17:43:17
Я не один из тех, кто отрицает, но обратите внимание, что стандартные PNRG действительно служат подлинной необходимости - т.е. иметь возможность повторного воспроизведения последовательности из известного семени. Иногда чистая стоимость настоящего криптографического ГСЧ слишком велика. И иногда необходим крипто-ГСЧ. Лошади на курсы, так сказать.
Marc Gravell♦ 20.04.2009 12:35:33
Согласно документации, этот класс является потокобезопасным, так что это что-то в его пользу.
Rob Church 26.04.2013 13:51:26
Какова вероятность того, что две случайные строки будут одним и тем же, используя это? Если строка состоит всего из 3 символов, я предполагаю, что это произойдет с большой вероятностью, но что, если длина 255 символов, может иметь одинаковую случайную строку или гарантировано, что это не может произойти из алгоритма?
Lyubomir Velchev 12.07.2016 13:03:08

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

Приветствия.

16
25.06.2009 17:24:34
Я бы переместил System.Environment.TickCount из цикла. Если во время итерации это произойдет, то у вас будет два объекта, инициализированных одним и тем же начальным числом. Другой вариант - объединить тик-счет a i по-другому (например, System.Environment.TickCount << 8 + i)
Dolphin 25.06.2009 19:03:12
Если я правильно понимаю: вы имеете в виду, что может случиться так, что "System.Environment.TickCount + i" может привести к ОДНОМУ ТОЛЬКО значению?
sabiland 26.06.2009 13:18:22
РЕДАКТИРОВАТЬ: Конечно, нет необходимости иметь TickCount внутри цикла. Виноват :).
sabiland 26.06.2009 13:19:12
В любом случае Random()конструктор по умолчанию вызываетRandom(Environment.TickCount)
Alsty 16.02.2017 16:29:40

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

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


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

Измерьте две реализации, и вы увидите значительную разницу.

62
23.06.2009 11:26:10
Блокировки очень дешевы, когда они не оспариваются ... и даже если бы они оспаривались, я бы ожидал, что код "теперь сделай что-нибудь с числом" уменьшит стоимость блокировки в большинстве интересных сценариев.
Marc Gravell♦ 18.09.2009 15:57:29
Согласен, это решает проблему блокировки, но разве это не очень сложное решение тривиальной проблемы: вам нужно написать «две» строки кода, чтобы сгенерировать случайное число вместо одного. Стоит ли экономить на чтении одной простой строки кода?
EMP 15.04.2010 23:07:37
+1 Использование дополнительного глобального Randomэкземпляра для получения семени - хорошая идея. Также обратите внимание, что код можно еще больше упростить, используя ThreadLocal<T>класс, представленный в .NET 4 (как Фил также написал ниже ).
Groo 9.05.2014 08:41:30

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

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);
118
27.08.2017 22:05:02

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

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

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

Мало того, что он медленнее (внутри цикла), его случайность ... ну, на мой взгляд, не очень хороша ..

40
23.05.2017 11:55:07
Я не согласен с делом Гуида. Класс Random реализует равномерное распределение. Что не так в Guid. Цель Guid - быть уникальным, а не распределенным равномерно (и его реализация в большинстве случаев основана на некотором свойстве оборудования / машины, которое противоположно ... случайности).
Askolein 31.03.2013 13:37:02
если вы не можете доказать однородность генерации Guid, то неправильно использовать его как случайный (а хеш будет еще одним шагом от однородности). Кроме того, столкновения не проблема: однородность столкновения. Относительно того, что поколение Guid больше не работает на аппаратном обеспечении, я собираюсь в RTFM, мой плохой (есть ссылка?)
Askolein 31.03.2013 14:18:01
Существует два понимания «случайного»: 1. отсутствие шаблона или 2. отсутствие шаблона после эволюции, описываемой распределением вероятностей (2 включены в 1). Ваш пример Guid верен в случае 1, а не в случае 2. Напротив: Randomкласс соответствует случаю 2 (таким образом, случай 1 тоже). Вы можете заменить использование Randomтолько вашим, Guid+Hashесли вы не в случае 2. Случай 1, вероятно, достаточно, чтобы ответить на вопрос, а затем, все Guid+Hashработает нормально. Но это не ясно сказано (пс: эта форма )
Askolein 31.03.2013 17:33:03
@Askolein Просто для некоторых тестовых данных, я запускаю несколько пакетов Randomи Guid.NewGuid().GetHashCode()через Ent ( fourmilab.ch/random ), и оба они одинаково случайны. new Random(Guid.NewGuid().GetHashCode())работает так же хорошо, как и использование синхронизированного «мастера» Randomдля генерации Randomначальных чисел для «дочерних элементов » . Конечно, это зависит от того, как ваша система генерирует Guids - для моей системы они довольно случайны, а для других это может даже быть крипто-случайным. Итак, Windows или MS SQL в настоящее время выглядят хорошо. Моно и / или мобильный может отличаться.
Luaan 21.04.2015 12:29:36
@EdB Как я уже говорил в комментариях ранее, хотя Guid (большое число) должен быть уникальным, GetHashCodeGuid в .NET является производным от его строкового представления. Вывод довольно случайный на мой вкус.
nawfal 3.08.2015 17:28:46

Есть много решений, здесь одно: если вы хотите, чтобы только цифры стерли буквы, а метод получил случайное число и длину результата.

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);
}
-1
4.06.2014 05:13:31
Основная проблема все та же - вы передаете Randomэкземпляр, но вы все еще ожидаете, что вызывающая сторона создаст общий экземпляр. Если вызывающий объект создает новый экземпляр каждый раз, а код выполняется дважды до изменения часов, вы получите одно и то же случайное число. Таким образом, этот ответ все еще делает предположения, которые могут быть ошибочными.
Savage 8.07.2019 10:38:33
Кроме того, весь смысл наличия метода для генерации случайных чисел заключается в инкапсуляции - что вызывающему методу не нужно беспокоиться о реализации, его интересует только возвращение случайного числа
Savage 8.07.2019 10:42:21

просто объявите переменную класса 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()один раз.

0
3.11.2018 08:35:52
Когда вы звоните, new Random()он использует системные часы, но если вы вызываете весь этот код дважды подряд, прежде чем часы изменятся, вы получите одно и то же случайное число. Вот и весь смысл ответов выше.
Savage 8.07.2019 10:36:03

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

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);
5
19.10.2018 21:17:00
It does not matter if you execute it millions of times, you will always use the same seed. Это неправда, если вы сами не укажете семя.
LarsTech 19.10.2018 17:47:38
Почини. Спасибо Точно так же, как вы говорите LarsTech, если всегда указывается одно и то же начальное число, всегда будет генерироваться одна и та же последовательность случайных чисел. В своем ответе я ссылаюсь на конструктор с параметрами, если вы всегда используете одно и то же начальное число. Класс Random генерирует только псевдослучайные числа. Если кто-то узнает, какое семя вы использовали в своем алгоритме, это может поставить под угрозу безопасность или случайность вашего алгоритма. С классом RNGCryptoServiceProvider вы можете безопасно иметь случайные числа. Я уже исправил, большое спасибо за исправление.
Joma 19.10.2018 21:03:09