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

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

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

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

16 ответов

Решение

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

Начиная с .NET 6 класс теперь оснащен статическим свойством с именем:

Предоставляет потокобезопасный экземпляр Random, который может использоваться одновременно из любого потока.

Вы можете использовать его следующим образом:

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

Доступ к потокобезопасному объекту имеет небольшие накладные расходы, поэтому, если вы планируете генерировать миллионы случайных чисел в одном потоке как можно быстрее, может быть предпочтительнее создать выделенныйRandomэкземпляр вместо того, чтобы полагаться наShared.

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

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

Теперь, начиная с .NET 6, есть простая в использовании и поточно-ориентированная альтернатива:Random.Shared

В вашем примере вы можете вообще удалить функциюRandomNumberа затем используйте следующий код (с той же логикой, но теперь он работает правильно):

      byte[] mac = new byte[6];
for (int x = 0; x < 6; ++x)
    mac[x] = (byte)(Random.Shared.Next(0, 255));

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

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

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

Вы можете использовать такой код:

      public static class ThreadSafeRandom
{
    private static readonly Random _global = new Random();
    private static readonly ThreadLocal<Random> _local = new ThreadLocal<Random>(() =>
    {
        int seed;
        lock (_global)
        {
            seed = _global.Next();
        }
        return new Random(seed);
    });

    public static Random Instance => _local.Value;
}

Этот код можно использовать как есть или через пакет NuGet ThreadSafeRandomizer.

Всегда получайте положительное случайное число.

 var nexnumber = Guid.NewGuid().GetHashCode();
        if (nexnumber < 0)
        {
            nexnumber *= -1;
        }

В Visual Basic это работает (возможно, его можно перевести на C#, если не решением может быть ссылка на DLL):

      Private Function GetRandomInt(ByVal Min As Integer, ByVal Max As Integer) As Integer
     Static Generator As System.Random = New System.Random()
     Return Generator.Next(Min, Max)
End Function

Я использую это:

      int randomNumber = int.Parse(Guid.NewGuid().ToString().FirstOrDefault(Char.IsDigit).ToString().Replace("\0", "0"));

Производительность: создание 1 миллиона случайных чисел на моем ПК: 711 мс.

Если Guid не содержит никакого числа (я не знаю, возможно это или нет), то в качестве результата будет использоваться 0.

Я решил проблему с помощью функции Rnd():

Function RollD6() As UInteger
        RollD6 = (Math.Floor(6 * Rnd())) + 1
        Return RollD6
End Function

Когда форма загружается, я использую метод Randomize(), чтобы убедиться, что я не всегда получаю одну и ту же последовательность случайных чисел от запуска к запуску.

Другие вопросы по тегам