Почему ГенераторСлучайныхЧисел в 1С выдает одинаковые числа и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Многие разработчики при работе с платформой 1С сталкиваются с парадоксальной ситуацией: программный код, использующий объект ГенераторСлучайныхЧисел, ведет себя совершенно по-разному в обычном режиме работы и при отладке. В режиме реальной эксплуатации программа генерирует массив абсолютно идентичных чисел, но стоит поставить точку останова и начать пошаговое выполнение (F10), как числа становятся «случайными». В этой статье мы разберем механизмы работы встроенного генератора, выясним причины такого поведения и рассмотрим правильные архитектурные подходы к генерации случайных данных.

Суть проблемы: почему отладка «исправляет» код?

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


Функция СгенеритьСЧ(Предел) Экспорт
    ГСЧ = Новый ГенераторСлучайныхЧисел;
    Возврат ГСЧ.СлучайноеЧисло(1, Предел);
КонецФункции

Функция МассивСЧ(Количество, Предел) Экспорт
    Результат = Новый Массив;
    Для Нум = 1 по Количество Цикл
        Результат.Добавить(СгенеритьСЧ(Предел));
    КонецЦикла;
    Возврат Результат;
КонецФункции

При выполнении функции МассивСЧ на сервере, создание объекта ГенераторСлучайныхЧисел происходит мгновенно. Если объект создается без параметров, 1С инициализирует его значением из системного таймера. Поскольку современные процессоры выполняют тысячи операций в миллисекунду, несколько вызовов функции СгенеритьСЧ подряд происходят в один и тот же квант времени. В результате каждый новый объект ГСЧ получает одинаковое начальное число (seed) и, согласно алгоритму псевдослучайных чисел, выдает одну и ту же последовательность.

Почему же в отладке все работает правильно? Ответ прост: пока разработчик анализирует строку кода или просто нажимает клавишу перехода к следующему шагу, системное время успевает измениться. Даже задержка в несколько миллисекунд, вносимая самим механизмом отладки, достаточна для того, чтобы следующая инициализация объекта Новый ГенераторСлучайныхЧисел получила новое значение таймера.

Решение №1: Однократная инициализация (Best Practice)

Самый эффективный и правильный с точки зрения производительности способ — инициализировать объект генератора один раз и использовать его многократно. Создание объекта — это всегда затраты ресурсов памяти и процессорного времени. Посмотрим, как переработать наш код:


Функция МассивСЧ(Количество, Предел) Экспорт
    // Инициализируем генератор ОДИН раз вне цикла
    ГСЧ = Новый ГенераторСлучайныхЧисел; 
    Результат = Новый Массив;
    
    Для Нум = 1 по Количество Цикл
        // Используем один и тот же объект для получения последовательности
        Результат.Добавить(ГСЧ.СлучайноеЧисло(1, Предел));
    КонецЦикла;
    
    Возврат Результат;
КонецФункции

В данном случае, даже если цикл выполнится за микросекунды, ГСЧ будет выдавать разные числа, так как он работает в рамках одной последовательности, начатой при создании объекта. Если же вам необходимо использовать генератор в разных функциях одного модуля, рассмотрим вариант хранения объекта в переменной модуля:


Перем мГенератор;

Процедура ИнициализироватьГенератор()
    Если мГенератор = Неопределено Тогда
        мГенератор = Новый ГенераторСлучайныхЧисел;
    КонецЕсли;
КонецПроцедуры

Функция ПолучитьСлучайноеЗначение(Предел) Экспорт
    ИнициализироватьГенератор();
    Возврат мГенератор.СлучайноеЧисло(1, Предел);
КонецФункции

Решение №2: Использование начального числа (Seed)

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

Разберем, какие значения лучше использовать в качестве начального числа:

  1. ТекущаяУниверсальнаяДатаВМиллисекундах(): Это наиболее точный вариант, доступный в современных версиях платформы 8.3. Вероятность совпадения миллисекунд при последовательных вызовах крайне мала.
  2. Комбинация составляющих даты: Для старых версий платформ можно использовать сумму секунд, минут и часов, однако это менее надежно.

Рассмотрим пример с использованием миллисекунд:


Функция СгенеритьСЧ_С_Инициализацией(Предел) Экспорт
    НачальноеЧисло = ТекущаяУниверсальнаяДатаВМиллисекундах();
    ГСЧ = Новый ГенераторСлучайныхЧисел(НачальноеЧисло);
    Возврат ГСЧ.СлучайноеЧисло(1, Предел);
КонецФункции

Важное замечание: Несмотря на кажущуюся надежность, этот метод все равно уступает первому решению. Если ваша задача — сгенерировать 1000 чисел, вызов конструктора 1000 раз существенно замедлит выполнение кода по сравнению с использованием одного объекта.

Анализ особенностей платформы 1С

Проанализируем требования Синтаксис-Помощника. В описании объекта ГенераторСлучайныхЧисел четко сказано: «Последовательность случайных чисел для одного и того же начального числа будет одинакова». Это фундаментальное свойство псевдослучайных генераторов (PRNG). Они не создают истинную случайность, а вычисляют значения по сложной математической формуле на основе «зерна» (seed).

Также стоит учитывать следующие моменты при работе на сервере:

Подведение итогов

Для корректной и быстрой работы со случайными числами в 1С придерживайтесь следующих правил:

  1. Избегайте создания объекта ГСЧ внутри циклов. Это главная причина дублирования значений и низкой производительности.
  2. Передавайте объект ГСЧ как параметр в подчиненные процедуры и функции, если требуется массовая генерация.
  3. Помните о разнице контекстов. На сервере код выполняется значительно быстрее, что обостряет проблему совпадения системного времени при инициализации.
  4. Используйте ТекущаяУниверсальнаяДатаВМиллисекундах() только в тех редких случаях, когда невозможно организовать хранение объекта генератора.

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

← На главную