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

662

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

//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" имеют равное значение.

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

  • 15
    использование new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256); не приводит к лучшим "случайным" числам, чем .Next(0, 256)
  • 0
    Вы можете найти этот пакет NuGet полезным. Он предоставляет статический Rand.Next(int, int) который обеспечивает статический доступ к случайным значениям, не блокируя и не сталкиваясь с проблемой повторного использования начального числа.
Теги:
random

9 ответов

859
Лучший ответ

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

Либо может быть хорошо; но mutexing одиночный экземпляр от нескольких вызывающих абонентов в то же время просто просит проблемы.

lock достигает первого (и более простого) этих подходов; однако другой подход может быть:

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

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

  • 1
    Я не уверен, что это ответ в этом случае, так как в вопросе говорится, что функция работает правильно, когда рядом с этим сегментом кода нет точек останова. Конечно, я согласен, что он должен использовать один экземпляр объекта Random, однако я не думаю, что это отвечает на его вопрос.
  • 0
    @ Джон - это достаточно сложно, просто пытаться держать его в поле зрения (в основном, как точка на среднем расстоянии).
Показать ещё 17 комментариев
94

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

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

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

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


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

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

  • 11
    Блокировки очень дешевы, когда они не оспариваются ... и даже если бы они оспаривались, я ожидал бы, что код "теперь сделай что-нибудь с номером" уменьшит стоимость блокировки в большинстве интересных сценариев.
  • 4
    Согласен, это решает проблему блокировки, но разве это не очень сложное решение тривиальной проблемы: вам нужно написать «две» строки кода, чтобы сгенерировать случайное число вместо одного. Это действительно стоит того, чтобы сохранить чтение одной простой строки кода?
Показать ещё 1 комментарий
32

Мой ответ здесь:

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

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

Итак, вы можете позвонить:

var i = Util.GetRandom();

.

Если для генерации случайных чисел вам необходим только истинный статический метод stateless, вы можете положиться на Guid.

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

Это будет немного медленнее, но может быть намного более случайным, чем Random.Next, по крайней мере, из моего опыта.

Но не:

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

Создание ненужного объекта сделает его медленнее, особенно в цикле.

И никогда:

new Random().Next();

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

  • 7
    Я не согласен с делом Гуида. Класс Random реализует равномерное распределение. Что не так в Guid. Цель Guid - быть уникальным, а не равномерно распределенным (и его реализация в большинстве случаев основана на некотором свойстве оборудования / машины, которое противоположно ... случайности).
  • 0
    @Askolein один, я не уверен в этом. Мы говорим о хэше guid, а не о самом guid. и это довольно случайно. Вы проверяли два? Я нахожу гораздо больше столкновений с Random.Next . Во-вторых, MS больше не генерирует guid на основе свойств оборудования.
Показать ещё 10 комментариев
22

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

byte[] random;
System.Security.Cryptography.RNGCryptoServiceProvider prov = new System.Security.Cryptography.RNGCryptoServiceProvider();
prov.GetBytes(random);
  • 30
    Я не один из тех, кто отрицает, но отмечу, что стандартные PNRG действительно служат подлинной необходимости - т.е. иметь возможность повторного воспроизведения последовательности из известного семени. Иногда чистая стоимость настоящего криптографического ГСЧ слишком велика. И иногда необходим крипто-ГСЧ. Лошади на курсы, так сказать.
  • 4
    Согласно документации, этот класс является потокобезопасным, так что это что-то в его пользу.
Показать ещё 1 комментарий
12

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).
  • 0
    Если я правильно понимаю: вы имеете в виду, что может случиться так, что «System.Environment.TickCount + i» может привести к ОДНОМУ ТОЛЬКО значению?
Показать ещё 2 комментария
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);
}
0

Если вы хотите использовать функцию, которая создает экземпляр Random каждый раз, когда вы можете использовать это:

Threading.Thread.Sleep(1)
Dim random As New Random(System.DateTime.Now.Millisecond)
Dim secret = random.Next(10000, 100000)
-1

Это приведет к генерации случайных чисел. Подстройте его по мере необходимости.

private static void Main(string[] args)
{
    var randoms = GetRandomNumbers(1, 1000, 50);

}

private static IEnumerable<int> GetRandomNumbers(int low, int high, int numberOfRandoms)
{
    Random rnd = new Random();
    var unique = new HashSet<int>();

    do
    {
        int num = rnd.Next(low, high);
        if (unique.Contains(num)) continue;
        unique.Add(num);
    } while (unique.Count < numberOfRandoms);

    return unique.ToList();
}

(== Try Me ==)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню