Генерация случайных чисел в .NET

Перевод статьи Random numbers широко известного в узких кругах Джона Скита. Остановился на этой статье, так как в своё время сам столкнулся с описываемой в ней проблемой.


Просматривая темы по .NET и C# на сайте StackOverflow, можно увидеть бесчисленное множество вопросов с упоминанием слова «random», в которых, по сути, поднимается один и тот же извечный и «неубиваемый» вопрос: почему генератор случайных чисел System.Random «не работает» и как это «исправить». Данная статья посвящена рассмотрению данной проблемы и способов её решения.

Постановка проблемы


На StackOverflow, в новостных группах и рассылках все вопросы по теме «random» звучат примерно так:
Я использую Random.Next для генерации нескольких случайных чисел, но метод возвращает одно и то же число при его множественных вызовах. Число меняется при каждом запуске приложения, однако в рамках одного выполнения программы оно постоянное.

В качестве примера кода приводится примерно следующее:
// Плохой код! Не использовать!
for (int i = 0; i < 100; i++)
{
     Console.WriteLine(GenerateDigit());
}
 ...
static int GenerateDigit()
{
     Random rng = new Random();
     // Предположим, что здесь много логики
     return rng.Next(10);
}

Итак, что здесь неправильно?

Объяснение


Класс Random не является истинным генератором случайных чисел, он содержит генератор псевдослучайных чисел. Каждый экземпляр класса Random содержит некоторое внутреннее состояние, и при вызове метода Next (или NextDouble, или NextBytes) метод использует это состояние для возврата числа, которое будет казаться случайным. После этого внутреннее состояние меняется таким образом, чтобы при следующем вызове метода Next он возвратил другое кажущееся-случайным число, отличное от возвращённого ранее.

Все «внутренности» работы класса Random полностью детерминистичны. Это значит, что если вы возьмёте несколько экземпляров класса Random с одинаковым начальным состоянием, которое задаётся через параметр конструктора seed, и для каждого экземпляра вызовите определённые методы в одинаковом порядке и с одинаковыми параметрами, то в конце вы получите одинаковые результаты.

Так что ж плохого в вышеприведённом коде? Плохо то, что мы используем новый экземпляр класса Random внутри каждой итерации цикла. Конструктор Random, не принимающий параметров, принимает значение текущей даты и времени как seed (начальное состояние). Итерации в цикле «прокрутятся» настолько быстро, что системное время «не успеет измениться» по их окончании; таким образом, все экземпляры Random получат в качестве начального состояния одинаковое значение и поэтому возвратят одинаковое псевдослучайное число.

Как это исправить?


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

Использование криптографического генератора случайных чисел

.NET содержит абстрактный класс RandomNumberGenerator, от которого должны наследоваться все реализации криптографических генераторов случайных чисел (далее — криптоГСЧ). Одну из таких реализаций содержит и .NET — встречайте класс RNGCryptoServiceProvider. Идея криптоГСЧ в том, что даже если он всё так же является генератором псевдослучайных чисел, он обеспечивает достаточно сильную непредсказуемость результатов. RNGCryptoServiceProvider использует несколько источников энтропии, которые фактически являются «шумами» в вашем компьютере, и генерируемую им последовательность чисел очень тяжело предсказать. Более того, «внутрикомпьютерный» шум может использоваться не только в качестве начального состояния, но и между вызовами следующих случайных чисел; таким образом, даже зная текущее состояние класса, этого не хватит для вычисления как следующих чисел, которые будут сгенерированы в будущем, так и тех, которые были сгенерированы ранее. Вообще-то точное поведение зависит от реализации. Помимо этого, Windows может использовать специализированное аппаратное обеспечение, являющееся источником «истинных случайностей» (например, это может быть датчик распада радиоактивного изотопа) для генерации ещё более защищённых и надёжных случайных чисел.

Сравним это с ранее рассматриваемым классом Random. Предположим, вы вызвали Random.Next(100) десять раз и сохранили результаты. Если вы имеете достаточно вычислительной мощи, то можете сугубо на основании этих результатов вычислить начальное состояние (seed), с которым был создан экземпляр Random, предсказать следующие результаты вызова Random.Next(100) и даже вычислить результаты предыдущих вызовов метода. Такое поведение катастрофически неприемлемо, если вы используете случайные числа для обеспечения безопасности, в финансовых целях и т.д. КриптоГСЧ работают существенно медленнее класса Random, но генерируют последовательность чисел, каждое из которых более независимо и непредсказуемо от значений остальных.

В большинстве случаев более низкая производительность не является препятствием — им является плохой API. RandomNumberGenerator создан для генерации последовательностей байтов — и всё. Сравните это с методами класса Random, где есть возможность получения случайного целого числа, дробного числа, а также набора байтов. Ещё одно полезное свойство — возможность получения случайного числа в указанном диапазоне. Сравните эти возможности с массивом случайных байтов, который выдаёт RandomNumberGenerator. Исправить ситуацию можно, создав свою оболочку (враппер) вокруг RandomNumberGenerator, которая будет преобразовывать случайные байты в «удобный» результат, однако это решение нетривиально.

Тем не менее, в большинстве случаев «слабость» класса Random вполне подходит, если вы сможете решить проблему, описанную в начале статьи. Посмотрим, что здесь можно сделать.

Используйте один экземпляр класса Random при множественных вызовах

Вот он, корень решения проблемы — использовать лишь один экземпляр Random при создании множества случайных чисел посредством Random.Next. И это очень просто — посмотрите, как можно изменить вышеприведённый код:
// Этот код будет получше
 Random rng = new Random();
for (int i = 0; i < 100; i++)
 {
     Console.WriteLine(GenerateDigit(rng));
 }
 ...
static int GenerateDigit(Random rng)
 {
     // Предположим, что здесь много логики
     return rng.Next(10);
 }

Теперь в каждой итерации будут разные числа, … но это ещё не всё. Что будет, если мы вызовем этот блок кода два раза подряд? Правильно, мы создадим два экземпляра Random с одинаковым начальным значением и получим два одинаковых набора случайных чисел. В каждом наборе числа будут различаться, однако между собой эти наборы будут равны.

Есть два способа решения проблемы. Во-первых, мы можем использовать не экземплярное, а статическое поле, содержащее экземпляр Random, и тогда вышеприведённый кусок кода создаст лишь один экземпляр, и будет его использовать, вызываясь сколько угодно раз. Во-вторых, мы можем вообще убрать оттуда создание экземпляра Random, переместив его «повыше», в идеале — на самый «верх» программы, где будет создан единичный экземпляр Random, после чего он будет передаваться во все места, где нужны случайные числа. Это отличная идея, которая красиво выражается зависимостями, но она будет работать до тех пор, пока мы используем лишь один поток.

Потокобезопасность


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

Снова-таки, здесь есть два пути решения проблемы. Первый путь по-прежнему предполагает использование одного экземпляра, однако на этот раз с использованием блокировки ресурса посредством монитора. Для этого необходимо создать оболочку вокруг Random, которая будет оборачивать вызов его методов в оператор lock, обеспечивающий эксклюзивный доступ к экземпляру для вызывающей стороны. Этот путь плох тем, что снижает производительность в многопоточно-интенсивных сценариях.

Другой путь, который я опишу ниже — использование по одному экземпляру на каждый поток. Единственное, нам нужно удостовериться, что при создании экземпляров мы используем разные начальные значения (seed), а потому мы не можем использовать конструкторы по умолчанию. Во всём остальном этот путь относительно прямолинеен.

Безопасный провайдер


К счастью, новый обобщённый класс ThreadLocal<T>, появившийся в .NET 4, очень сильно облегчает написание провайдеров, обеспечивающих по одному экземпляру на поток. Просто нужно в конструктор ThreadLocal передать делегат, который будет ссылаться на получение значения собственно нашего экземпляра. В данном случае я решил использовать единственное начальное значение (seed), инициализируя его при помощи Environment.TickCount (именно так действует конструктор Random без параметров). Далее полученное количество тиков инкрементируется каждый раз, когда нам нужно получить новый экземпляр Random для отдельного потока.

Нижепредставленный класс полностью статический и содержит лишь один публичный (открытый) метод GetThreadRandom. Этот метод сделан методом, а не свойством, в основном из-за удобства: благодаря этому все классы, которым нужен экземпляр Random, будут зависеть от Func<Random> (делегат, указывающий на метод, не принимающий параметров и возвращающий значение типа Random), а не от самого класса Random. Если тип предназначен для работы в одном потоке, он может вызвать делегат для получения единого экземпляра Random и после чего использовать его повсюду; если же тип должен работать в многопоточных сценариях, он может вызывать делегат каждый раз, когда ему требуется генератор случайных чисел. Нижеприведенный класс создаст столько экземпляров класса Random, сколько есть потоков, и каждый экземпляр будет стартовать с различного начального значения. Если нам нужно использовать провайдер случайных чисел как зависимость в других типах, мы можем сделать так: new TypeThatNeedsRandom(RandomProvider.GetThreadRandom). Ну а вот и сам код:
using System;
using System.Threading;
  
public static class RandomProvider
{    
     private static int seed = Environment.TickCount;
     
     private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
         new Random(Interlocked.Increment(ref seed))
     );

     public static Random GetThreadRandom()
     {
         return randomWrapper.Value;
     }
}

Достаточно просто, не правда ли? Всё потому, что весь код направлен на выдачу правильного экземпляра Random. После того, как экземпляр создан и возвращён, совершенно неважно, что вы будете с ним делать дальше: все дальнейшие выдачи экземпляров совершенно не зависят от текущего. Конечно, клиентский код имеет лазейку для злонамеренного неправильного использования: он может получить один экземпляр Random и передать его в другие потоки вместо вызова в тех, других потоках, нашего RandomProvider.

Проблемы с дизайном интерфейса


Одна проблема всё равно остаётся: мы используем слабо защищённый генератор случайных чисел. Как упоминается ранее, существует намного более безопасная во всех отношениях версия ГСЧ в RandomNumberGenerator, реализация которого находится в классе RNGCryptoServiceProvider. Однако его API достаточно сложно использовать в стандартных сценариях.

Было бы очень приятно, если бы провайдеры ГСЧ в фреймворке имели отдельные «источники случайности». В таком случае мы могли бы иметь единый простой и удобный API, который бы поддерживался как небезопасной-но-быстрой реализацией, так и безопасной-но-медленной. Что-ж, мечтать не вредно. Возможно, подобный функционал появится в следующих версиях .NET Framework. Возможно, кто-то не из Microsoft предложит свою реализацию адаптера. (К сожалению, я не буду этим кем-то… правильная реализация подобной задумки удивительно сложна.) Вы также можете создать свой класс, отнаследовав его от Random и переопределив методы Sample и NextBytes, однако неясно, как именно они должны работать, и даже собственная реализация Sample может быть намного сложнее, нежели кажется. Может быть, в следующий раз…
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 23

    +4
    Иногда требуется детерминированное поведение, например, в тестах. По идее можно предусмотреть в RandomProvider'е возможность выдавать заранее инициализированный ГПСЧ и использовать этот вариант в тестах. При этом должна быть возможность его сбрасывать в исходное состояние, чтобы результат не зависел от порядка выполнения тестов.
      0
      Абсолютно согласен.

      Кстати, Джим Митчелл реализовал Random-like оболочку вокруг RNGCryptoServiceProvider: Cryptographically Secure Random Numbers. И хотя она, возможно, не столь идеальна, как того хотелось бы Джону Скиту, ею вполне возможно пользоваться. Не хотел это вставлять в статью ради чистоты перевода, так сказать.
      +1
      С каждым днем все больше удивляешься насколько много крутых изменений в .NET 4 по поводу многопоточности.
        –4
        Казалось бы, времена ассемблера давно прошли… Ан нет, все ещё приходится писать страницы кода чтобы получить простой тривиальный результат, вроде вычисления случайного числа. Платформы наращивают номера с все убыстряющимся темпом, а программисту почему-то не сильно легчает от этого. Может не в том направлении шагаем?
          +1
          А кто вам сказал, что получение случайного значения — это простая/тривиальная задача?
            0
            А почему Вы решили, что я имел в виду алгоритмическую сложность?
            r = rand(1);
            одна строчка, одно действие — в переменную r попадает (псевдо)случайное значение в диапазоне [0..1). Если нет никаких подводных камней вроде проблем с инициализацией и тредовой безопасностью, можно спокойно программировать дальше то что задумал, и не отвлекаться на низкоуровневые вещи. Я ж сравнение с ассемблером не зря упомянул.
              0
              Практика показывает, что если Вы пишете что-то сложнее курсовой младших курсов института, то подводные камни — есть.
              Просто Вы о них(пока еще) не знаете. Но в продакшне вылезет.
              То же rand() без srand() — а, от запуска к запуску будет выдавать одно и то же. Ну то есть игра, например, в нарды будет вызывать чувство дежа вю. А если с srand()-ом с сидом с таймера — то как воспроизвести в тестах? Да и вообще — воспроизвести.
                –1
                А не кажется ли Вам, что в rand() можно встроить проверку был ли инициализирован srand()? И ещё сделать его сразу threadsafe? Т.е. избавить сотни тысяч! программистов от необходимости тратить миллионы человеко-часов на всю эту рутину и избавить их от необходимости выискивать неочевидные ошибки? А для тех, кому действительно надо, — оставить возможность при необходимости покопаться под капотом, как-то выставить начальное состояние принудительно или ещё что-то, для тех же тестов.
                  0
                  > А не кажется ли Вам, что в rand() можно встроить проверку был ли инициализирован srand()?
                  Он всегда инициализирован. По умолчанию единицей. Что дает полностью предсказуемый результат. Для тех, кого не устраивает, как раз и сделали функцию srand(). Просто про нее надо знать.
                  > И ещё сделать его сразу threadsafe?
                  Да запросто. С бесплатным бонусом в виде возросшего на порядок времени исполнения и малопредсказуемыми дедлоками.
                  > и избавить их от необходимости выискивать неочевидные ошибки?
                  Вы хотите сказать — добавить им необходимости выискивать неочевидные ошибки?

                  Собственно, rand() так и сделан — предельно просто и очевидно. И потому для реальных целей малоприменим. Все равно придется писать объекты-обертки, если не вообще менять алгоритм генерации, т.к. дефолтная реализация, скажем прямо, не жжет.

                  Языки программирования, они как правило делаются не для студентов все же, а для коммерческого ПО. Где подразумевается, что пишущие имеют представление о том, как оно должно работать. Поэтому C#-овский рандом сделан именно так — минимализм и расширяемость в ущерб удобству использования «из коробки». Зато максимально прозрачно.
                    +1
                    Зачем придираться к словам? Разумеется у генератора будет стартовое состояние после создания, но я ведь имел ввиду «инициализацию» как подготовку к выдаче различающихся псевдослучайных чисел, а не одного и того же потока для каждого запуска программы. Ведь в 99.(9)% случаев программисту надо именно просто какое-то похожее на случайное число, и крайне желательно чтобы оно было НЕ предсказуемым. А srand() то как раз сделали для того чтобы получать ПРЕДСКАЗУЕМЫЙ поток, а вовсе не для тех, кого он не устраивает )

                    Да прямо уж на порядок. )) Звучит страшно, а давайте аккуратно проанализируем? Для одного потока блокиратор только один, а значит конкуренции за мютекс ему ждать неоткуда — так что тут оверхед микродоли процента. Для нескольких потоков — необходимо лочить сид чтобы им не воспользовался паралельный поток для получения такого же числа. Можно лочить втупую, до тех пор пока не посчитаем новое значение, а можно в умную, проинкрементив сид и отпустив лок (чтобы паралельный поток получил отличающееся число без ожидания мютекса), и потом проапдейтить сид, когда новое значение будет высчитано (тут и лок без необходимости). Опять оверхед очень и очень далек от «возросшего на порядок времени»…

                    >Вы хотите сказать — добавить им необходимости выискивать неочевидные ошибки?
                    Вы, очевидно, не пытаетесь понять мою мысль. Если программисту нужно случайное непредсказуемое число, то в чем вообще может произойти ошибка, если он его получит легко и без напряга?

                    >И потому для реальных целей малоприменим.
                    Вдумайтесь в свои слова ) Если реализовано малоприменимое решение, вместо применимого, то налицо архитектурный дефект.
                      0
                      Про синхронизацию — бред.

                      Время выполнения Monitor.Enter/Monitor.Exit раза в 3-4 больше чем у Random.GetDouble.

                      Микродоли процента — это в ваших тёплых снах. А в hi-performance computing локи — это не такая радуждая вещь. Почитайте на досуге о lock free synchronizations. Например у Альбахари.
                        0
                        Слово spinlock вам ничего не говорит? Мьютексы, они, знаете ли, очень разные бывают ) Правда, не знаю особенности реализации блокировок в .NET, поэтому не стану утверждать, что на этой платформе thread-safety реализуется без больших накладных расходов.
                          0
                          1. Мьютексы при невозможности взять лок (когда он занят уже) на .NET под виндой уходят в код ядра. Это дико медленно на фоне генерации ПСЧ. Подозреваю что раз в 50 больше времени занимает.

                          2. Spinlock вам ничем не поможет. Его взятие будет ещё дороже чем Monitor.Enter/Exit. Да, он эффективнее для случаев когда очень большая concurrency, но опять же, потерять 90% перфоманса это слишком.

                          Thread-safety реализуется на любой платформе с огромными накладными расходами на фоне расходов на генерацию ПСЧ.
                            0
                            1. Это везде так, ведь надо шедулеру управление передать и усыпить тред. Но это самый тяжелый случай, когда лок занят надолго, на блокирующейся операции например, или долгих вычислениях…

                            2. Гм, у Вас неверное представление о spinlock-ах — это самые дешевые (в плане взятия) и быстрые (в плане времени на взятие/освобождение) локи, но они создают большую дополнительную нагрузку на cpu в тех тредах, которые пытаются захватить заблокированный ресурс, поэтому должны использоваться недолго. Например, при обновлении переменной.

                            3. Thread-safety сегодня — насущная необходимость, и уже много ресурсов было брошено на оптимизацию этого вопроса, так что не все так страшно, как было 10 лет назад.
                              0
                              Последний раз. Потокобезопасный генератор псевдослучайных чисел (разумеется если брать непотокобезопасный алгоритм) будет в разы медленнее его непотокобезопасного аналога при применении в однопоточном контексте (это наиболее используемый сценарий).

                              Медленнее он будет потому что любая синхронизация на практике окажется дороже генерации ПСЧ (несомненно есть тяжёлые алгоритмы генерации ПСЧ, но я говорю о простейших).

                              То же самое проецируется на коллекции.

                              Вы можете свои теоретические познания продолжить изливать в тред, но к сожалению практика иная. И я перед тем как писать всё же создал проект и прогнал там тесты на время. Давайте вы почитаете Albahari и этим закончим. Вот вам cсылочка.
                              www.albahari.com/threading/
                                0
                                За ссылку спасибо. Хоть я и не под виндой программирую, но немного интересно, что и как на другой стороне происходит.
                                Согласен, что дискуссию можно завершать, ведь генерация ПСЧ действительно происходит быстро и не должна включать в себя переключение контекста, да и изначально мой месседж состоял в том что инструментарий для программисто надо готовить тщательно, а не так чтобы его потом ещё напильником надо было дорабатывать.

                                Тем не менее, есть один момент касательно
                                Spinlock вам ничем не поможет. Его взятие будет ещё дороже чем Monitor.Enter/Exit

                                Смотрим http://www.albahari.com/threading/part5.aspx#_SpinLock_and_SpinWait:
                                SpinLock and SpinWait are structs and not classes! This design decision was an extreme optimization technique to avoid the cost of indirection and garbage collection.

                                The SpinLock struct lets you lock without incurring the cost of a context switch, at the expense of keeping a thread spinning (uselessly busy).


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

                                В частности, если лочить весь процесс генерации ПСЧ, от взятия текущего сида до его обновления, то это ограничит производительность rand() максимум одним ядром минус оверхед (ок, пусть даже десятки процентов). Но я не представляю себе ситуации, где может потребоваться большая производительность для rand(), так что сделать его thread-safe все-таки стоило бы из коробки )
                      +1
                      Вот правильная удобная реализация: random.seed([x])
                      Initialize the basic random number generator. Optional argument x can be any hashable object. If x is omitted or None, current system time is used; current system time is also used to initialize the generator when the module is first imported. If randomness sources are provided by the operating system, they are used instead of the system time (see the os.urandom() function for details on availability).


                      Вот эта программа будет всегда генерировать случайное число на основании таймера при запуске:
                      import random
                      print(random.random())
                      

                      интепретатор в первой команде (импорт модуля) сам сделал srand, выполнив его обёртку random.seed() c аргументом «текущее время» или даже /dev/random в Linux и CryptGenRandom в Windows, т.е. реально настоящее случайное.

                      А вот эта программа будет выдавать одно и то же значение:
                      import random
                      random.seed(1)
                      print(random.random())
                      

                      интепретатор сделал srand, но мы потом сделали ещё один, уже сознательно, зная, что хотим повторения последовательности.

                      Подход .Net в данном случае — плохой, он заставляет изобретать велосипед каждого, кто захочет генерировать случайные числа. Не факт, что программист на C# с ходу напишет такую же продвинутую логику, какую сделали разработчики Python в библиотеке random — скорее наоборот, он пока дойдёт до такой логики перелопатит кучу документации и всё равно где-нибудь допустит оплошность. Подход Python позволяет гораздо быстрее и дешевле получить как минимум тот же, а в большинстве случаев более качественный результат.
            –4
            Насколько я помню из школьного курса информатики — есть ровно два способа получить не псевдослучайное число. Один из них связан с радиоактивным распадом, а второй — с кипением какого-то масла в большом-большом чане. Во втором случае якобы измеряют координаты пузырей растворённого газа и их объём. Хоть я иногда и думаю, что с чаном информатик над нами пошутил.
              0
              Если мы говорим о *действительно* случайных числах — то только квантовые аппаратные генераторы, там жестко подперто, что за рандомность отвечает вселенная в лице квантовой механики.
              На практике такая пунктуальность не особенно важна, поэтому используются разнообразные шумы из драйверов, типа младшего бита на линейном входе аудиокарточки.
                0
                Есть такая штука — Гиперион, спутник планеты Сатурн. Его центр масс движется вокруг Сатурна по нормальной эллиптической орбите с предсказуемыми возмущениями от других небесных тел, но вот сам спутник при этом хаотически кувыркается. Его положение в пространстве невозможно предсказать на сколь-нибудь длинный (по астрономическим меркам) промежуток времени, в отличие от положения собственно на орбите, которое предсказывается очень точно надолго. Это такой, знаете ли, совершенно неквантовый генератор случайных чисел.

                На Земле таких же примеров полно. Например, можно подобрать такой поток воды, капающей из крана, чтобы попасть в режим динамического хаоса — интервалы между каплями будут совершенно случайными. То же самое, никаким способом вы не предскажете, каким будет следующий интервал.

                А ещё турбулентность, кипение жидкостей и тому подобное.
                  +1
                  Вы не очень удачный пример подобрали, хотя и интересный.

                  Любая неквантовая система эволюционирует по собственному закону, которое можно выразить некоторым дифф. уравнением. На малых временных масштабах дифф. уравнение решается точно, если дано точное исходное состояние. Стало быть всё, всякий ГСЧ на таких системах будет опирать только на то, что исходное состояние нам не известно. Для хаотических систем это работает, хотя опять же на малых масштабах по времени будет наблюдаться детерменизм.

                  Вы правильно сказали: «Его положение в пространстве невозможно предсказать на сколь-нибудь длинный (по астрономическим меркам) промежуток времени».
                  Однако даже системное время в мс (или даже просто в секундах) можно использовать для генерации СЧ в небольшом диапазоне, если это требуется, скажем, раз в сутки/неделю/месяц. Опять же, это вопрос о масштабах, в нашем мире нет ни одной полностью детерминированной системы.
                  Автор статьи же ставит вопрос о том, что делать, если генерировать нужно много и сразу. Можно ли использовать предложенные Вами методы для генерации 100500+ СЧ в ед. времени? Не получится.

                  Квантовые генераторы — это другой уровень. При измерении физ. величины состояние коллапсирует в собственное состояние физ. величины вероятностно, притом эти вероятности определяются квадратом модуля коэффициентов разложения состояния по базису из собственных состояний физ. величины.
                  Случайность является истинной, неподдельной, поэтому всё как бы хорошо. Но там могут возникнуть проблемы, связанные с эволюцией состояния во времени: нестационарное состояние будет меняться во времени детерминированным образом вплоть до следующего измерения, однако может понадобиться некоторое время для того, чтоб отойти от собственного состояний физ.величины, которое было получено при предыдущем измерении.
                  Но эти порядки совершенно другие, нам столько информации сразу не нужно.
              +1
              Как дополнение к вышесказанному, использование Mersenne Twister во многих случаях дает более адекватный результат чем стандартный Random.
                0
                Большое спасибо!
                Я, честно сказать, даже и не думал о том, что класс Random потоконебезопасен. Всегда хранил его в статическом поле. Теперь, видимо, не буду =)
                Кстати, буквально сегодня столкнулся с вариантом получения случайных чисел, в котором использовался Random, а seed для него вычислялся не из времени, а через RNGCryptoServiceProvider.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое