Многие разработчики при работе с платформой 1С сталкиваются с парадоксальной ситуацией: программный код, использующий объект ГенераторСлучайныхЧисел, ведет себя совершенно по-разному в обычном режиме работы и при отладке. В режиме реальной эксплуатации программа генерирует массив абсолютно идентичных чисел, но стоит поставить точку останова и начать пошаговое выполнение (F10), как числа становятся «случайными». В этой статье мы разберем механизмы работы встроенного генератора, выясним причины такого поведения и рассмотрим правильные архитектурные подходы к генерации случайных данных.
Проанализируем типичный пример кода, который часто приводит к ошибке. Представим ситуацию, когда разработчик создает вспомогательную функцию для получения одного случайного числа и вызывает её в цикле:
Функция СгенеритьСЧ(Предел) Экспорт
ГСЧ = Новый ГенераторСлучайныхЧисел;
Возврат ГСЧ.СлучайноеЧисло(1, Предел);
КонецФункции
Функция МассивСЧ(Количество, Предел) Экспорт
Результат = Новый Массив;
Для Нум = 1 по Количество Цикл
Результат.Добавить(СгенеритьСЧ(Предел));
КонецЦикла;
Возврат Результат;
КонецФункции
При выполнении функции МассивСЧ на сервере, создание объекта ГенераторСлучайныхЧисел происходит мгновенно. Если объект создается без параметров, 1С инициализирует его значением из системного таймера. Поскольку современные процессоры выполняют тысячи операций в миллисекунду, несколько вызовов функции СгенеритьСЧ подряд происходят в один и тот же квант времени. В результате каждый новый объект ГСЧ получает одинаковое начальное число (seed) и, согласно алгоритму псевдослучайных чисел, выдает одну и ту же последовательность.
Почему же в отладке все работает правильно? Ответ прост: пока разработчик анализирует строку кода или просто нажимает клавишу перехода к следующему шагу, системное время успевает измениться. Даже задержка в несколько миллисекунд, вносимая самим механизмом отладки, достаточна для того, чтобы следующая инициализация объекта Новый ГенераторСлучайныхЧисел получила новое значение таймера.
Самый эффективный и правильный с точки зрения производительности способ — инициализировать объект генератора один раз и использовать его многократно. Создание объекта — это всегда затраты ресурсов памяти и процессорного времени. Посмотрим, как переработать наш код:
Функция МассивСЧ(Количество, Предел) Экспорт
// Инициализируем генератор ОДИН раз вне цикла
ГСЧ = Новый ГенераторСлучайныхЧисел;
Результат = Новый Массив;
Для Нум = 1 по Количество Цикл
// Используем один и тот же объект для получения последовательности
Результат.Добавить(ГСЧ.СлучайноеЧисло(1, Предел));
КонецЦикла;
Возврат Результат;
КонецФункции
В данном случае, даже если цикл выполнится за микросекунды, ГСЧ будет выдавать разные числа, так как он работает в рамках одной последовательности, начатой при создании объекта. Если же вам необходимо использовать генератор в разных функциях одного модуля, рассмотрим вариант хранения объекта в переменной модуля:
Перем мГенератор;
Процедура ИнициализироватьГенератор()
Если мГенератор = Неопределено Тогда
мГенератор = Новый ГенераторСлучайныхЧисел;
КонецЕсли;
КонецПроцедуры
Функция ПолучитьСлучайноеЗначение(Предел) Экспорт
ИнициализироватьГенератор();
Возврат мГенератор.СлучайноеЧисло(1, Предел);
КонецФункции
Если архитектура приложения не позволяет хранить объект (например, вызовы происходят из разных независимых контекстов), можно использовать принудительную инициализацию уникальным числом. Платформа позволяет передать в конструктор число, которое послужит основой для алгоритма.
Разберем, какие значения лучше использовать в качестве начального числа:
Рассмотрим пример с использованием миллисекунд:
Функция СгенеритьСЧ_С_Инициализацией(Предел) Экспорт
НачальноеЧисло = ТекущаяУниверсальнаяДатаВМиллисекундах();
ГСЧ = Новый ГенераторСлучайныхЧисел(НачальноеЧисло);
Возврат ГСЧ.СлучайноеЧисло(1, Предел);
КонецФункции
Важное замечание: Несмотря на кажущуюся надежность, этот метод все равно уступает первому решению. Если ваша задача — сгенерировать 1000 чисел, вызов конструктора 1000 раз существенно замедлит выполнение кода по сравнению с использованием одного объекта.
Проанализируем требования Синтаксис-Помощника. В описании объекта ГенераторСлучайныхЧисел четко сказано: «Последовательность случайных чисел для одного и того же начального числа будет одинакова». Это фундаментальное свойство псевдослучайных генераторов (PRNG). Они не создают истинную случайность, а вычисляют значения по сложной математической формуле на основе «зерна» (seed).
Также стоит учитывать следующие моменты при работе на сервере:
Повторное использование возвращаемых значений. В этом случае 1С запомнит первый результат и будет возвращать его всем пользователям без выполнения кода.Для корректной и быстрой работы со случайными числами в 1С придерживайтесь следующих правил:
ТекущаяУниверсальнаяДатаВМиллисекундах() только в тех редких случаях, когда невозможно организовать хранение объекта генератора.Выполнение этих простых рекомендаций гарантирует, что ваше приложение будет работать стабильно и предсказуемо как в режиме отладки, так и на рабочих серверах под высокой нагрузкой.