Многопоточность. Универсальный «Менеджер потоков» 2.0
Администрирование - Оптимизация БД (HighLoad)
"Фоновые задания" потоки "менеджер потоков" фоновых заданий" распараллелить "восстановление партий" многопоточность
Изменения (возможно придется править события разработчика)
- Данные рассчитываемые перед запуском нового потока теперь доступны в потоке(ах) по имени "ПараметрыРазработчикаПередЗапускомПотока" (до этого имя было: "ДанныеРарзаботчикаПередЗапускомПотока")
- Данные рассчитываемые при запуске нового потока теперь доступны в потоке(ах) по имени "ПараметрыРазработчикаПриЗапускеПотока" (до этого имя было: "ДанныеРарзаботчикаПриЗапускеПотока")
Исправленные ошибки:
- При работе с методом "ОбработатьОбъект" могла возникать ошибка (Спасибо Земко Сергей, за найденную ошибку):
- {ОбщийМодуль.мпМенеджерПотоков.Модуль(1514)}: Поле объекта не обнаружено (АлгоритмыРаботыСКоллекцией)
- КодСоздания = пСтруктураПараметров.ПараметрыИнициализации.ПараметрыОбработкиКоллекции.АлгоритмыРаботыСКоллекцией.КодСоздания;
- Событие "ПослеЗавершенияМенеджераПотоков" теперь ВСЕГДА выполняется самым последним. Имели место случаи, когда данное событие вызывалось ДО события "ПередЗавершениемМенеджераПотоков".
- Внесены корректировки в обработку коллекции без потоков (параметр определяется от объема коллекции и параметров инициализации):
- Результат события "ПередЗапускомПотока", теперь доступны по имени "ПараметрыРазработчикаПередЗапускомПотока" (до этого имя было: "ДанныеДляПотока")
- Добавлено выполнение события "ПриЗапускеПотока"
- Добавлено выполнение события "ПриОбработкеМассиваОбработанныхФагментовКоллекции"
- Добавлено выполнение события "ПриОбработкеДополненногоФагментаКоллекции"
- Исключено выполнение события "ПриОбработкеОбработанногоФагментаКоллекции"
P.S. Теперь обработка коллекции без потоков полностью имитирует вызов событий при работе в потоках.
Небольшая оптимизация:
- Скорректированно ожидание "пустых" циклов с 1 сек. до 0.1 сек. (данные циклы исполняются для синхронизации работы фоновых заданий уже после полной обработки данных)
Исправленные ошибки:
- При выполнении метода "ДополнитьКоллекциюОбъектов", возникала ошибка при окончательной сборки новой коллекции.
- При выполнении метода "ПолучитьСтруктуруПараметровИнициализацииМенеджераПотоков", если в качестве второго параметра выступали ДвоичныеДанные внешней обработки, не происходила инициализация менеджера потоков.
Небольшая оптимизация:
- После того, как менеджер потоков завершал работу, можно было увидеть большое количество пустых сообщений. Теперь сообщения выводятся, только если они выводились в событии "ПриОбработкеДействияПотока".
Движок сайта не позволяет при публикации за $m, выкладывать бесплатно.
Вложил презентацию в основную поставку
Бесплатные и условно бесплатные решения и статьи:
- TaskManagerFor1C - Автор: Евгений Павлюк;
- Многопоточность как способ ускорения некоторых процедур - Автор статьи: Алексей Бочков; (Показан САМЫЙ простой способ организации многопоточности)
- Многопоточная обработка данных - Автор: Алексанрдр Зыков; (Надстройка над конфигурацией позволяющая реализовать многопоточную обработку данных, с настройкой в пользовательском режиме, с отчетами и мониторингом)
- Многопоточное восстановление последовательностей - Автор: Алексанрдр Зыков; (представлен механизм реализации восстановления последовательностей в ЛЮБОЙ конфигурации и использованием "Многопоточной обработки данных". ПРЕДУПРЕЖДЕНИЕ для реализации в типовых конфигурациях может потребоваться корректировка типовых объектов метаданных)
Платные решения и статьи:
- Ускорение процесса восстановления последовательности в 1С 8 УПП с использованием параллельных вычислений - Автор статьи Softpoint (весьма познавательная статья с картинками)
- Параллельное восстановление партий - Автор: 1С-Рарус (Тут больше о достижении 1С-Рарус в решении данного вопроса, а также представлены результаты одного из заказчиков)
Все, что было в первой версии, переработано, улучшено и дополнено. В связи с чем она (первая версия) переходит в разряд статьи с основными объяснениями и картинками.
Сразу договоримся, если в тексте будет указано «v1» - это отсылка к реализации в первой версии, если «v2» - к текущей.
Очень кратко, о чем речь…
Фреймворк в виде одного общего модуля, позволяющего при получении объектов на обработку запускать их в несколько потоков. Особенности:
- Нет необходимости рассчитывать «порции» для обработки;
- Нет необходимости организовывать файловый обмен между потоками;
- Возможность запускать несколько менеджеров потоков одновременно, при этом потоки одного менеджера, могут запускать новые менеджеры со своими задачами и потоками;
- Можно выстраивать граф зависимости объектов, что позволяет, например, избегать взаимоблокировок и/или организовать восстановление партий (на нашем предприятии удалось добиться 10х+ ускорения при 10 потоках в рабочее время – 200+ активных пользователей - Результаты работы механизма);
- Все необходимые "вмешательство" в алгоритмы происходят с помощью событий;
- Возможность описывать алгоритмы событий, как в модуле менеджера, так и в любом другом модуле БД (предпочтительно), а также во внешней обработке.
- Автоматический рестарт потока в случае ошибок;
- Контроль за количеством рестартов по каждому объекту;
- Возможность получать «ответы» от потоков;
- Возможность контролировать работу с помощью «Инструментов разработчика» или иных отчетов;
- Возможность срочного прерывания работы;
- И многое другое…
так было в «v1»
Основные изменения v2:
- Только полная версия, полностью открытый код;
- Практически полностью переписано ядро и архитектура (кода стало на ~50% больше), механизм обмена данными остался тот же (Хранилище общих настроек);
- Появилось 3 способа обработки:
- Обработка поэлементно:
- Процедура ОбработатьОбъект(); (реализовано v1)
- Обработка коллекций:
- Процедура ОбработатьКоллекцию();
- Процедура ДополнитьКоллекцию ().
- Обработка поэлементно:
- Скорость обработки «зависимых» объектов происходит быстрее на 5-15% по сравнению с «v1», при выполнении одной и той же задачи;
- Появилась возможность передавать в потоки произвольные единожды сформированные данные или рассчитывать их при запуске потока.
- Возможность получать «ответ» об обработке объекта(ов) в «реальном времени» (в «v1» приходилось дополнительно прописывать обвязку из временного хранилища с помощью «СобытийРазработчика», откуда данные можно было получить только в конце обработки);
- Сообщения выведенные в потоках, теперь выводятся автоматически (в «v1» приходилось обрабатывать через «Событиях разработчика»);
- Расчет ресурсов теперь выполняют потоки;
- Изменен состав «Событий разработчика»;
- Изменена структура параметров передаваемых в «События разработчика»;
- Граф теперь является одним объектом (в «v1» он состоял из нескольких не связанных объектов, что могло вызвать затруднения в понимании работы);
- Расширены собираемые данные для анализа работы менеджера потоков (до 11 показателей);
- Предоставлен шаблон функции «ОбработатьСобытиеРазработчика» для своих модулей;
- В статью добавлены примеры;
- Прочие мелочи.
Теперь обо всем по порядку в деталях и с картинками...
При реализации v1, мне действительно удалось добиться поставленной цели (ускорить восстановление партий в УПП (не РАУЗ) в РАЗЫ), но были некоторые моменты вызывающие неудовлетворенность. Вот схема, примера организации обмена данными для получения ответа по v1. Проблемы обозначил цифрами:
-
1 проблема - расчет ресурса для построения графа. «Менеджер потоков» реализовывался для ускорения работы «Основной программы», а нам пришлось навесить на нее дополнительную работу по расчету ресурсов объектов. (Проведенные тесты показали, что выполнение алгоритма восстановления партий за 1 день, без непосредственного восстановления – просто выборка документов из последовательности и выполнение команд в цикле без проведения, выполняется за 30 сек., но стоит нам добавить расчет ресурсов и эта же работы выполнялась уже за 90 сек.).
- Факты за:
- Основная программа рассчитывает ресурсы и «отбраковывает» объекты не требующие обработки (при восстановлении партий в нашей БД из Результаты работы механизма видно, что таких документов почти 50%);
- «Снижение» нагрузки на СХД – т.к. не все передаются для обработки.
- Факты против:
- По замерам было видно, что периоды, когда не задействовано ни одного потока доходили до 30% времени работы;
- «Основная программа» дополнительно нагружена вычислением ресурсов.
- Факты за:
- 2 проблема – временное хранилище - это «костыль» который каждый раз приходиться описывать разработчику, чтобы иметь возможность получить ответы о результатах обработки объектов.
- 3 проблема – «Менеджер потоков» - это прокладка, в буквальном смысле. Данные из основной программы попадают в потоки, только пройдя «насквозь» через «Менеджер потоков», а проходя «насквозь» приходиться: писать(основная программа)-читать(менеджер), писать(менеджер)-читать(поток), писать(поток)-читать(менеджер) и в конце с помощью «проблемы 2» консолидировано писать(менеджер)-читать(основная программа).
- 4 проблема – длительное ожидание ответа. Ответ в «основной программе» можно было получить только после окончания работы «менеджера потоков» через «проблему 2».
Новая реализация (v2) движения данных:
На первый взгляд может показаться все очень сложно, и количество записей 6 против 4 в v1, но обо всем по порядку:
- 1 – «Основная программа» записывает в хранилище все необходимые данные для обработки.
- 2 – «Основная программа» записывает в зарезервированную для «Менеджера потоков» ячейку массив адресов с размещенными данными.
- 3 – «Менеджер потоков» на каждой итерации опрашивает ячейку с информацией о размещении новых данных.
- 4 – «Менеджер потоков» - считывает полученные данные для проведения анализа и построения графа (несвязанного).
- 5 – «Менеджер потоков» записывает в зарезервированные за каждым потоком ячейки, адреса, откуда потоку взять данные.
- 6 – «Поток» на каждой итерации опрашивает зарезервированную за собой ячейку на предмет появления информации о размещении данных.
- 7 – «Поток» по полученному адресу (п.6) считывает и обрабатывает данные.
- 8 – «Поток» после обработки формирует ответ и записывает его, туда же, откуда были считаны данные.
- 9 – «Поток» при штатном завершении обработки данных производит очистку своей зарезервированной ячейки.
- 10 – «Менеджер потоков» на каждой итерации производит опрос ячеек зарезервированные за потоками, куда были переданы адреса хранения данных для обработки. На данном этапе «Менеджер потоков» отслеживает активность потоков и в случае их «падения» запускает новый поток.
- 11 – «Менеджер потоков» считывает результат обработки и производит дальнейший анализ, при необходимости строит новый граф (связанный) и повторяет п.п. 5-10 (уже для обработки объекта по связям) или см. п. 12.
- 12 – «Менеджер потоков» записывает в зарезервированную для «Основной программы» ячейку информацию о результатах обработки.
- 13 – «Основная программа» на каждой итерации опрашивает ячейку с информацией о результатах обработки.
- 14 – «Основная программа» - считывает результат обработки объекта.
Теперь видно, что данные (они могут быть очень большими) записываются только 1 раз и 1 раз записывается результат обработки, а 4 раза записывается только служебная (короткая) информации. В v1 из-за проблемы 3, данные записывались 2 раза и 2 раза записывался результат обработки.
Результаты по ранее описанным проблемам:
- 1 проблема - расчет ресурса для построения графа. Теперь расчетом ресурсов занимается потоки, а не основная программа.
- 2 проблема - временное хранилище. Теперь в нем нет необходимости. Ответ возвращается туда, куда были помещены данные, обработка происходит через вызываемые события.
- 3 проблема – «Менеджер потоков» - это прокладка. Теперь менеджер потоков - это именно менеджер. Он управляет данными и сообщает другим участникам «Основной программе» и «Потокам», когда, откуда, какую (для «Основной программы») и для каких целей (для «Потоков») взять информацию.
- 4 проблема – длительное ожидание ответа. Теперь результат обработки «Основная программа» получает сразу после окончания обработки объекта.
В v2, реализован дополнительные способы обработки данных. Теперь их три:
Данный способ обработки предназначен для выстраивания зависимостей объектов и их обработки в определенной последовательности (например, восстановление партий, восстановление последовательности расчетов и т.д.).
СтруктураПараметров =
мпМенеджерПотоков.ПолучитьСтруктуруПараметровИнициализацииМенеджераПотоков("Тест", Новый ДвоичныеДанные(ЭтотОбъект.ИспользуемоеИмяФайла));
Для каждого ОбъектСсылка из МассивОбъектов Цикл
//тут формируется структура параметров необходимых для объекта
СтрукутраПараметровОбъекта = ...;
мпМенеджерПотоков.ОбработатьОбъект(СтруктураПараметров, ОбъектСсылка, СтрукутраПараметровОбъекта);
КонецЦикла;
мпМенеджерПотоков.ДождатьсяОстановкиМенеджераПотоков(СтруктураПараметров);
текущий способ был изначально реализован в v1 и по сути не изменился.
Следующие 2 метода работают сразу с коллекцией значений (массив, список значений и таблица значений), но с некоторыми отличиями;
Результатом работы является произвольный объект. Элементом обрабатываемых данных в потоках является фрагмент коллекции. Размер фрагмента зависит от разных параметров уточняемых при инициализации.
СтруктураПараметров = мпМенеджерПотоков.ПолучитьСтруктуруПараметровИнициализацииМенеджераПотоков("Тест", Новый ДвоичныеДанные(ЭтотОбъект.ИспользуемоеИмяФайла));
ПроизвольныеДанные = мпМенеджерПотоков.ОбработатьКоллекциюОбъектов(СтруктураПараметров, Коллекция);
Именно при решении таких задач v1, начинала проигрывать стандартным способам, например таким: //1c-club.pro/public/182139/. Основная проблема заключалась в затратах на передачу данных поэлементно. В связи с чем, была реализована «обертка» позволяющая разбивать коллекцию на фрагменты и посылать в потоки на обработку сразу несколько элементов. Так же реализован контроль порядка фрагментов коллекции передаваемой в обработку, вне зависимости от времени обработки каждого из фрагментов порядок будет соответствовать первичной «Коллекции».
Результатом работы является новый объект идентичной структуры и с тем же порядком данных, что и в переданной коллекции. Элементом обрабатываемых данных в потоках является элемент коллекции.
СтруктураПараметров = мпМенеджерПотоков.ПолучитьСтруктуруПараметровИнициализацииМенеджераПотоков("Тест", Новый ДвоичныеДанные(ЭтотОбъект.ИспользуемоеИмяФайла));
НоваяКоллекция = мпМенеджерПотоков.ДополнитьКоллекциюОбъектов(СтруктураПараметров, Коллекция);
Можно воспользоваться данным методом, когда не получается «достать/обработать» данные простым запросом сразу по всем объектам. По сути - это доработка метода «ОбработатьКоллекциюОбъектов» - т.к. с его помощью можно добиться такого же результата, но с бОльшими усилиями.
Теперь все параметры структурированы по месту своего возникновения:
- ПараметрыИнициализации;
- ПараметрыОсновнойПрограммы / ПараметрыМенеджераПотоков / ПараметрыПотока;
- ПараметрыРазработчика;
- ПараметрыОбщие.
Отдельно можно сказать только про «ПараметрыРазработчика» - сюда разработчику следует помещать свои произвольные данные и сюда же помещаются результаты работы некоторых событий.
Для работы с коллекциями в структуру параметров было добавлено еще 2 параметра:
- КоличествоЭлементовКолекцииНаПоток – название говорит само за себя. Число (по умолчанию = 0);
- ДинамическийРассчетКоличестваПотоков – позволяет рассчитать количество необходимых потоков, но не больше, чем параметр «КоличествоПотоков». Булево (по умолчанию = Ложь).
Если эти параметры оставить по умолчанию, то обработка коллекции всегда будет идти в потоках (по умолчанию «КоличествоПотоков» = 10), даже если элементов в коллекции будет 4. Просто 6 потоков будут запущены зря.
- Размер коллекции = 10 000, КоличествоЭлементовКолекцииНаПоток = 1 500, ДинамическийРассчетКоличестваПотоков = Ложь.
- Результат: Количество запущенных потоков = 10.
- Размер коллекции = 1 000, КоличествоЭлементовКолекцииНаПоток = 1 500, ДинамическийРассчетКоличестваПотоков = Ложь.
- Результат: Количество запущенных потоков = 0. Все выполнение будет происходить в основной программе с эмуляцией вызова событий разработчика, т.к. там прописана вся логика обработки коллекции.
- Размер коллекции = 10 000, КоличествоЭлементовКолекцииНаПоток = 1 500, ДинамическийРассчетКоличестваПотоков = Истина.
- Результат: Количество запущенных потоков = 7.
- Размер коллекции = 100 000, КоличествоЭлементовКолекцииНаПоток = 1 500, ДинамическийРассчетКоличестваПотоков = Истина.
- Результат: Количество запущенных потоков = 10.
- Размер коллекции = 1 000, КоличествоЭлементовКолекцииНаПоток = 1 500, ДинамическийРассчетКоличестваПотоков = Истина.
- Результат: Количество запущенных потоков = 0. Все выполнение будет происходить в основной программе с эмуляцией вызова событий разработчика, т.к. там прописана вся логика обработки коллекции.
Данный раздел так же претерпел некоторые изменения. И вот что получилось:
Пришлось убрать одно событие («ПриОпределенииТипДанныхВПотоке») – честно рад этому, меня оно раздражало J
С точки зрения большего понимания (моего понимания) были изменены названия других событий, место вызова и назначение.
И добавить новые.
Также из-за изменений архитектуры, часть событий поменяли свое место «жительства» (вызов), но кроме того, они изменили способ своей работы с процедуры на функцию. Сделано это для того, чтобы дать возможность передать произвольную информацию, как из «Основной программы» в «Менеджер потоков» или в «Потоки», так и из «Менеджера потоков» в «Потоки». Чтобы поток при обработке объекта или фрагмента коллекции не собирал эту информацию снова или не кэшировал ее «где придется».
Также поменялся состав параметров событий. Если раньше это была хаос из параметров и у каждого события они могли быть уникальны, то теперь параметров 2 обязательных:
- «ИмяСобытия»;
- «СтруктураПараметров»;
И 1 переменный
- «СтруктураДанных» - обычно это та структура, которая передается на обработку.
Детали «Событий разработчика» описаны в самом низу модуля:
//ШАБЛОН ФУНКЦИИ Для своего произвольного модуля
а укороченный вариант представлен в самой функции ОбработатьСобытиеРазработчика()
//МЕСТО ДЛЯ АЛГОРИТМА РАЗРАБОТЧИКА
Шаблон специально добавил, т.к. сам частенько забывал, как с какими событиями работать.
Доступность событий от методов:
Для понимания, что и как работает, предоставлено 3 отчета для мониторинга деятельности:
- МониторингПорядкаОбработкиОбъектов (На стороне «Основной программы»);
- МониторингОчередиНаОбработку (На стороне «Основной программы»);
- МониторингМенеджераПотоков (На стороне «Менеджера потоков» - сервер 1С).
Для включения каждого мониторинга необходимо в событии разработчика «ПередЗапускомМенеджераПотоков» или сразу после «ПолучитьСтруктуруПараметровИнициализацииМенеджераПотоков» установить значение «Истина» (по умолчанию они установлены в значение «Ложь») для необходимого параметра.
Функция ПередЗапускомМенеджераПотоков(пПараметрыСобытия);
пПараметрыСобытия.СтруктураПараметров.ПараметрыИнициализации.ВестиМониторингМенеджераПотоков = Истина;
пПараметрыСобытия.СтруктураПараметров.ПараметрыИнициализации.ВестиМониторингПорядкаОбработкиОбъектов = Истина;
пПараметрыСобытия.СтруктураПараметров.ПараметрыИнициализации.ВестиМониторингОчередиНаОбработку = Истина;
Кроме того, необходимо в событии разработчика «ПриПолученииМестаХраненияФайловМониторинга», прописать способ определения пути для каждого из результатов.
Функция ПриПолученииМестаХраненияФайловМониторинга(пПараметрыСобытия)
ИмяТаблицы = пПараметрыСобытия.СтруктураДанных.ИмяТаблицы;
Если ИмяТаблицы = "МониторингОчередиНаОбработку" Тогда
Путь = "Z:\01 ТаблицаОчередиНаОбработку.xlsx";
ИначеЕсли ИмяТаблицы = "МониторингПорядкаОбработкиОбъектов" Тогда
Путь = "Z:\01 ТаблицаПорядкаОбработкиОбъектов.xlsx";
ИначеЕсли ИмяТаблицы = "МониторингМенеджераПотоков" Тогда
Путь = "Z:\01 ТаблицаМониторингМенеджера.xlsx";
Иначе
Путь = Неопределено;
КонецЕсли;
Возврат Путь;
КонецФункции
Имена таблиц: «МониторингОчередиНаОбработку», «МониторингПорядкаОбработкиОбъектов», «МониторингМенеджераПотоков» - зарезервированы.
Данный мониторинг достаточно прост, но позволяет получить реальную картину обработки объектов, какие объекты, когда и как были обработаны. Вот результат сохраненного отчета:
Где:
- ВремяВмс – ТекущаяУниверсальнаяДатаВМиллисекундах()
- Обработанный объект – ссылка или значение обрабатываемого объекта;
- Результат обработки – собственно как объект был обработан. Возможные варианты:
- Ответ - объект обработан штатно и получен «Ответ»;
- Пропуск – по каким-то причинам (событие разработчика «ПриДобавленииВОчередьОбработки» ) было принято решение не включать объект в обработку;
- Ошибка – было произведено несколько попыток (СтруктураПараметров.ПараметрыИнициализации.ПределКоличествоПопытокОбработатьОбъект) обработать объект, но все они закончились неудачно;
- ДополненныйФрагментКоллекции
- ОбработанныйФрагментКоллекции
Объекты обведенные рамкой, это уже ручная работа J, чтоб показать, что ответ приходит в один момент времени по нескольким объектам.
С помощью данного отчета можно увидеть (правда не в 2а клика), какие объекты не соответствуют ожидаемой последовательности, и разобраться с ресурсами.
Тоже весьма простой отчет, показывает, как быстро растет очередь на обработку и растет ли. Данные получаются на каждой итерации перед попыткой отправить данные в менеджер потоков. Вот результаты сохраненного отчета:
Где:
- ВремяВмс – ТекущаяУниверсальнаяДатаВМиллисекундах()
- РазмерОчередиВМП – текущий размер очереди для передачи в «Менеджер потоков»;
- К_Свободно – Количество свободных ячеек для обмена с «Менеджером потоков»,
- К_РассчитатьРесурсы – Название колонки содержит название действия («Рассчитать ресурсы») которое необходимо произвести на объектом. Значение показывает, сколько объектов на данной итерации находиться в обработке «Менеджером потоков». Возможные действия:
- Рассчитать ресурсы (см.);
- Дополнить коллекцию (см. );
- Обработать коллекцию (см. ).
Внимательный читатель мог заметить и озадачиться, почему если отчет собирает данные на каждой итерации, то в колонке «РазмерОчередиВМП» вдруг появляются «2» при свободных ячейках. Это связано с тем, что «Основная программа» «пытается» отправить данные в «Менеджер потоков» и если она видит, что данные отправленные в прошлой итерации еще не приняты, то на текущей итерации отправка не происходит, зато в следующей итерации при наличии свободных ячеек, будет отправлено максимальное возможное количество объектов. Вот так выглядит график очереди при восстановлении партий за 1 день в нашей организации, за 57 сек. обработана 1113 документов, при 10 потоках.
На данном графике видны «горбы» - это говорит о том, что цикл перебора объектов «Основной программы» работает быстрее, чем справляются «Потоки» с обработкой и это в принципе правильно. Но к 9 сек. потоки успели обработать всю накопленную очередь (188 док за 9 сек.), т.к. ~ с 5 сек. в выборку стало попадать много документов «Поступление товаров и услуг» (это видно по отчету «Мониторинг порядка обработки объектов») не требующих обработки и очередь упала практически до 0. С одной стороны это хорошо, мы успеваем обрабатывать объекты в «Потоках» со скоростью перебора в цикле «Основной программы» (плюс затраты на пересылку). Но с другой стороны плохо, т.к. потоки получается запущенны напрасно. По факту работает 1-2 и все. Ближе к концу снова начинают появляться документы требующие обработки и очередь снова начинает расти.
Мониторинг менеджера потоков
Данный отчет позволяет увидеть все, что происходит в менеджере потоков на каждой итерации, а именно:
- Какой размер графа;
- Сколько объектов получено на обработку;
- Сколько объектов передано в потоки и для каких действий;
- Сколько объектов уже находятся в потоках и какие действия над ними выполняются;
- Сколько объектов ожидают отправки в потоки и для каких действий;
- Сколько объектов и с каким результатом отправлено «Основной программе».
Колонок в отчете может быть очень много и состав их может быть разный в зависимости от выбранного способа обработки. Покажу лишь скриншоты получаемых графиков в Excel (такой график настроить не просто, придется потратить минут 5-10).
Небольшие пояснения:
- расстояние между 2мя вертикальными линиями - это 1 итерация «Менеджера потоков» (М_Итерация);
- «Клиент» – это «Основная программа».
Запуск:
Работа в разгаре:
«Выход на плато» к 11 сек, когда очередь на обработку иссякла (см. Мониторинг очереди на обработку).
Ну и ближе к финишу: к 58 сек. расчет ресурсов завершен. В графе находится 16 объектов, но обрабатываются они только в 3 потока, т.к. «цепляются» друг за друга ресурсами.
Теперь посмотрим как с этим работать.
Пойдем от просто к сложному: ДополнитьКоллекцию – ОбработатьКоллекцию – ОбработатьОбъект.
- Количество потоков = 10 (по умолчанию);
- Количество элементов на поток = 0 (по умолчанию);
- ДинамическийРассчетКоличестваПотоков = Ложь (по умолчанию);
- Коллекция – Список значений
- Размер коллекции 10 000 / 100 000;
- Значение = порядковый номер элемента коллекции.
Функция СоздатьКоллекцию(РазмерКоллекции = 10000)
Коллекция = Новый СписокЗначений;
Для Сч = 1 по РазмерКоллекции Цикл
Коллекция.Добавить(Сч);
КонецЦикла;
Возврат Коллекция;
КонецФункции
Процедура ЛинейноНажатие(Элемент)
Коллекция = СоздатьКоллекцию();
ВремяСтарт = ТекущаяУниверсальнаяДатаВМиллисекундах();
Если СпособОбработки = "ДополнитьКоллекциюПометка" Тогда
//...
КонецЕсли;
РассчитатьПродолжительность(ВремяСтарт, "Линейно");
КонецПроцедуры
Процедура ВПотокахНажатие(Элемент)
Коллекция = СоздатьКоллекцию();
ВремяСтарт = ТекущаяУниверсальнаяДатаВМиллисекундах();
СтруктураПараметров = мпМенеджерПотоков.ПолучитьСтруктуруПараметровИнициализацииМенеджераПотоков(СпособОбработки, Новый ДвоичныеДанные(ЭтотОбъект.ИспользуемоеИмяФайла));
НоваяКоллекция = мпМенеджерПотоков.ДополнитьКоллекциюОбъектов(СтруктураПараметров, Коллекция);
РассчитатьПродолжительность(ВремяСтарт, СтрШаблон("В %1 потоков", СтруктураПараметров.ПараметрыИнициализации.КоличествоПотоков));
КонецПроцедуры
Процедура обработки потоков останется неизменной – весь код будет в общем модуле внешней обработки в «событиях разработчика»
Данный способ обработки подразумевает следующую идею: послав коллекцию на обработку, на выходе вы получаете ТОЧНО ТАКУЮ ЖЕ КОЛЛЕКЦИЮ (но не ту же что посылали на обработку), но с пересчитанными/заполненными данными. ВАЖНО!!! в поток для обработки передается ЭЛЕМЕНТ КОЛЛЕКЦИИ.
Если СпособОбработки = "ДополнитьКоллекциюПометка" Тогда
Для каждого ЭлементКоллекции из Коллекция Цикл
ЭлементКоллекции.Пометка = Истина;
КонецЦикла;
Функция ОбработатьСобытиеРазработчика(пПараметрыСобытия) Экспорт
ИмяСобытия = пПараметрыСобытия.ИмяСобытия;
СтруктураПараметров = пПараметрыСобытия.СтруктураПараметров;
РазрезМенеджеров = СтруктураПараметров.ПараметрыИнициализации.РазрезМенеджеров;
ОтветСобытия = Неопределено;
Если РазрезМенеджеров = "ДополнитьКоллекциюПометка" Тогда
Если ИмяСобытия = "ПриОбработкеДействияПотока" Тогда //Поток
ОтветСобытия = ДополнитьКоллекциюПометка_ПриОбработкеДействияПотока(пПараметрыСобытия);
КонецЕсли;
КонецЕсли;
Возврат ОтветСобытия;
КонецФункции
//******************************************************
Функция ДополнитьКоллекциюПометка_ПриОбработкеДействияПотока(пПараметрыСобытия)
ЭлементКоллекции = пПараметрыСобытия.СтруктураДанных;
ЭлементКоллекции.Пометка = Истина;
КонецФункции
Результат выполнения в потоках – заметно проигрывает из-за накладных расходов на распараллеливание и из-за ничтожной сложности операции.
ИначеЕсли СпособОбработки = "ДополнитьКоллекциюЗаменаНаНоменклатуруВЦикле" Тогда
ДлинаКода = Метаданные.Справочники.Номенклатура.ДлинаКода;
СпрНоменклатура = Справочники.Номенклатура;
Для каждого ЭлементКоллекции из Коллекция Цикл
Код = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
ЭлементКоллекции.Значение = СпрНоменклатура.НайтиПоКоду(Код);
КонецЦикла;
Функция ОбработатьСобытиеРазработчика(пПараметрыСобытия) Экспорт
ИмяСобытия = пПараметрыСобытия.ИмяСобытия;
СтруктураПараметров = пПараметрыСобытия.СтруктураПараметров;
РазрезМенеджеров = СтруктураПараметров.ПараметрыИнициализации.РазрезМенеджеров;
ОтветСобытия = Неопределено;
Если РазрезМенеджеров = "ДополнитьКоллекциюЗаменаНаНоменклатуруВЦикле" Тогда
Если ИмяСобытия = "ПриОбработкеДействияПотока" Тогда //Поток
ОтветСобытия = ДополнитьКоллекциюЗаменаНаНоменклатуру_ПриОбработкеДействияПотока(пПараметрыСобытия);
КонецЕсли;
КонецЕсли;
Возврат ОтветСобытия;
КонецФункции
//******************************************************
Функция ДополнитьКоллекциюЗаменаНаНоменклатуру_ПриОбработкеДействияПотока(пПараметрыСобытия)
ЭлементКоллекции = пПараметрыСобытия.СтруктураДанных;
ДлинаКода = Метаданные.Справочники.Номенклатура.ДлинаКода;
СпрНоменклатура = Справочники.Номенклатура;
Код = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
ЭлементКоллекции.Значение = СпрНоменклатура.НайтиПоКоду(Код);
КонецФункции
В данном примере операция вроде примитивна, всего-то найти по коду, но это множество микро запросов к серверу СУБД (запрос в цикле) и тут уже потоки выигрывают, т.к. запросы распараллелены.
Кто-то скажет, надо один раз выбрать данные и дальше по ним искать, ок…
ИначеЕсли СпособОбработки = "ДополнитьКоллекциюЗаменаНаНоменклатуру1Запрос" Тогда
ДлинаКода = Метаданные.Справочники.Номенклатура.ДлинаКода;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.Код ИЗ Справочник.Номенклатура КАК Номенклатура";
ТЗ = Запрос.Выполнить().Выгрузить();
Для каждого ЭлементКоллекции из Коллекция Цикл
Код = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
Строка = ТЗ.Найти(Код, "Код");
ЭлементКоллекции.Значение = ?(Строка = Неопределено, Неопределено, Строка.Ссылка);
КонецЦикла;
Функция ОбработатьСобытиеРазработчика(пПараметрыСобытия) Экспорт
ИмяСобытия = пПараметрыСобытия.ИмяСобытия;
СтруктураПараметров = пПараметрыСобытия.СтруктураПараметров;
РазрезМенеджеров = СтруктураПараметров.ПараметрыИнициализации.РазрезМенеджеров;
ОтветСобытия = Неопределено;
Если РазрезМенеджеров = "ДополнитьКоллекциюЗаменаНаНоменклатуру1Запрос" Тогда
Если ИмяСобытия = "ПриЗапускеПотока" Тогда //Поток
ОтветСобытия = ДополнитьКоллекциюЗаменаНаНоменклатуру1Запрос_ПриЗапускеПотока(пПараметрыСобытия);
ИначеЕсли ИмяСобытия = "ПриОбработкеДействияПотока" Тогда //Поток
ОтветСобытия = ДополнитьКоллекциюЗаменаНаНоменклатуру1Запрос_ПриОбработкеДействияПотока(пПараметрыСобытия);
КонецЕсли;
КонецЕсли;
Возврат ОтветСобытия;
КонецФункции
//******************************************************
Функция ДополнитьКоллекциюЗаменаНаНоменклатуру1Запрос_ПриЗапускеПотока(пПараметрыСобытия)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.Код ИЗ Справочник.Номенклатура КАК Номенклатура";
Структура = Новый Структура;
Структура.Вставить("ДлинаКода",Метаданные.Справочники.Номенклатура.ДлинаКода);
Структура.Вставить("ТаблицаНоменклатуры", Запрос.Выполнить().Выгрузить());
Возврат Структура;
КонецФункции
//******************************************************
Функция ДополнитьКоллекциюЗаменаНаНоменклатуру1Запрос_ПриОбработкеДействияПотока(пПараметрыСобытия)
ДанныеРарзаботчикаПриЗапускеПотока =
пПараметрыСобытия.СтруктураПараметров.ПараметрыРазработчика.ДанныеРарзаботчикаПриЗапускеПотока;
ДлинаКода = ДанныеРарзаботчикаПриЗапускеПотока.ДлинаКода;
ТЗ = ДанныеРарзаботчикаПриЗапускеПотока.ТаблицаНоменклатуры;
ЭлементКоллекции = пПараметрыСобытия.СтруктураДанных;
Код = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
Строка = ТЗ.Найти(Код, "Код");
ЭлементКоллекции.Значение = ?(Строка = Неопределено, Неопределено, Строка.Ссылка);
КонецФункции
Прошу обратить внимание, как произошла передача данных из одного события в другое. Сначала мы получили данные в событии «ПриЗапускеПотока», а затем воспользовались его результатами в событии «ПриОбработкеДействияПотока». Таким же образом можно передать данные от «Основной программы» в «Менеджер потоков» и в сами «Потоки». Детали в шаблоне функции в самом конце общего модуля «Менеджера потоков».
Время выросло, стало даже больше, чем просто «НайтиПоКоду» - связано это с выгрузкой запроса в таблицу значений, там более 100 к строк (да у нас такой большой справочник J) и с поиском по данной таблице.
Сразу следует вопрос «А где фильтр в запросе?». Вот он..
ИначеЕсли СпособОбработки = "ДополнитьКоллекциюЗаменаНаНоменклатуру1ЗапросСФильтром" Тогда
ДлинаКода = Метаданные.Справочники.Номенклатура.ДлинаКода;
МассивКодов = Новый Массив;
Для каждого ЭлементКоллекции из Коллекция Цикл
ЭлементКоллекции.Значение = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
МассивКодов.Добавить(ЭлементКоллекции.Значение);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.Код ИЗ Справочник.Номенклатура КАК Номенклатура
|ГДЕ Номенклатура.Код В (&МассивКодов)";
Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
ТЗ = Запрос.Выполнить().Выгрузить();
Для каждого ЭлементКоллекции из Коллекция Цикл
Строка = ТЗ.Найти(ЭлементКоллекции.Значение, "Код");
ЭлементКоллекции.Значение = ?(Строка = Неопределено, Неопределено, Строка.Ссылка);
КонецЦикла;
Из-за технических особенностей, такой финт не получиться провернуть в данном способе обработки («ДополнитьКоллекциюОбъектов») в потоках. Причина: в потоках есть 2 события «ПриЗапускеПотока» и «ПриОбработкеДействияПотока». Первое событие срабатывается в самом начале запуска потока, когда поток еще не получил ни какие данные и оно подходит, только для установки первоначальных констант. Второе событие срабатывает при обходе каждого элемента фрагмента коллекции и приведет к запросу в цикле.
Но данную оптимизацию можно реализовать с помощью другого способа обработки «ОбработатьКоллекциюОбъектов», т.к. там в событие «ПриОбработкеДействияПотока» передается весь фрагмент, но об этом позже (Пример 5).
На большом объеме, скорость обработки падает не пропорционально увеличенному объему - проблемы с поиском в большой таблице значений.
Данный способ обработки подразумевает следующую идею: послав коллекцию на обработку, на выходе вы получаете ПРОИЗВОЛЬНЫЙ ТИП ДАННЫЙ. ВАЖНО!!! в поток для обработки передается ФРАГМЕНТ КОЛЛЕКЦИИ.
Все что у нас меняется в обработке – это вызов метода:
//было:
//НоваяКоллекция = мпМенеджерПотоков.ДополнитьКоллекциюОбъектов(СтруктураПараметров, Коллекция);
//
//стало:
НоваяКоллекция = мпМенеджерПотоков.ОбработатьКоллекциюОбъектов(СтруктураПараметров, Коллекция);
Вернемся к недоделанному примеру 4
ИначеЕсли (СпособОбработки = "ДополнитьКоллекциюЗаменаНаНоменклатуру1ЗапросСФильтром")
ИЛИ (СпособОбработки = "ОбработатьКоллекциюЗаменаНаНоменклатуру1ЗапросСФильтром") Тогда
ДлинаКода = Метаданные.Справочники.Номенклатура.ДлинаКода;
МассивКодов = Новый Массив;
Для каждого ЭлементКоллекции из Коллекция Цикл
ЭлементКоллекции.Значение = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
МассивКодов.Добавить(ЭлементКоллекции.Значение);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.Код ИЗ Справочник.Номенклатура КАК Номенклатура
|ГДЕ Номенклатура.Код В (&МассивКодов)";
Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
ТЗ = Запрос.Выполнить().Выгрузить();
Для каждого ЭлементКоллекции из Коллекция Цикл
Строка = ТЗ.Найти(ЭлементКоллекции.Значение, "Код");
ЭлементКоллекции.Значение = ?(Строка = Неопределено, Неопределено, Строка.Ссылка);
КонецЦикла;
Функция ОбработатьСобытиеРазработчика(пПараметрыСобытия) Экспорт
ИмяСобытия = пПараметрыСобытия.ИмяСобытия;
СтруктураПараметров = пПараметрыСобытия.СтруктураПараметров;
РазрезМенеджеров = СтруктураПараметров.ПараметрыИнициализации.РазрезМенеджеров;
ОтветСобытия = Неопределено;
Если РазрезМенеджеров = "ОбработатьКоллекциюЗаменаНаНоменклатуру1ЗапросСФильтром" Тогда
Если ИмяСобытия = "ПриОбработкеДействияПотока" Тогда //Поток
ОтветСобытия = ОбработатьКоллекциюЗаменаНаНоменклатуру1ЗапросСФильтром_ПриОбработкеДействияПотока(пПараметрыСобытия);
ИначеЕсли ИмяСобытия = "ПриОбработкеМассиваОбработанныхФагментовКоллекции" Тогда //Основная программа
ОтветСобытия = ОбработатьКоллекциюЗаменаНаНоменклатуру1ЗапросСФильтром_ПриОбработкеМассиваОбработанныхФагментовКоллекции(пПараметрыСобытия);
КонецЕсли;
КонецЕсли;
Возврат ОтветСобытия;
КонецФункции
//******************************************************
Функция ОбработатьКоллекциюЗаменаНаНоменклатуру1ЗапросСФильтром_ПриОбработкеДействияПотока(пПараметрыСобытия)
Коллекция = пПараметрыСобытия.СтруктураДанных;
ДлинаКода = Метаданные.Справочники.Номенклатура.ДлинаКода;
МассивКодов = Новый Массив;
Для каждого ЭлементКоллекции из Коллекция Цикл
ЭлементКоллекции.Значение = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
МассивКодов.Добавить(ЭлементКоллекции.Значение);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.Код ИЗ Справочник.Номенклатура КАК Номенклатура
|ГДЕ Номенклатура.Код В (&МассивКодов)";
Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
ТЗ = Запрос.Выполнить().Выгрузить();
Для каждого ЭлементКоллекции из Коллекция Цикл
Строка = ТЗ.Найти(ЭлементКоллекции.Значение, "Код");
ЭлементКоллекции.Значение = ?(Строка = Неопределено, Неопределено, Строка.Ссылка);
КонецЦикла;
Возврат Коллекция;
КонецФункции
//******************************************************
Функция ОбработатьКоллекциюЗаменаНаНоменклатуру1ЗапросСФильтром_ПриОбработкеМассиваОбработанныхФагментовКоллекции(пПараметрыСобытия)
МассивСтруктур = пПараметрыСобытия.СтруктураДанных;
НоваяКоллекция = Новый СписокЗначений;
Для каждого СтруктураОтвета из МассивСтруктур Цикл
ФрагментКоллекции = СтруктураОтвета.ОтветПотока;
КолИндексовКоллекции = ФрагментКоллекции.Количество() - 1;
Для ИндексКоллекции = 0 по КолИндексовКоллекции Цикл
ЗаполнитьЗначенияСвойств(НоваяКоллекция.Добавить(), ФрагментКоллекции[ИндексКоллекции]);
КонецЦикла;
КонецЦикла;
Возврат НоваяКоллекция;
КонецФункции
При небольшом объеме скорость сопоставима, но при увеличении объема обработка в потоках дает заметное ускорение в работе.
Однако еще одна проблема медленной работы еще не устранена...
Основная проблема медленно работы – это поиск по таблице значений. Решение простое, но не всегда применимое – индексирование.
Тут код приводить не буду, просто добавляем в коде (см. пример 5) и в «линейном», и в «потоках» после:
ТЗ = Запрос.Выполнить().Выгрузить();
ТЗ.Индексы.Добавить("Код");
В данном варианте линейное выполнение опять выигрывает из –за оптимальности поиска (фильтр по индексу и таблице БД, и в таблице значение) и отсутствия необходимости производить дополнительные операции распараллеливания.
Но попадание в индекс не всегда возможно и такую оптимизацию ни всегда можно применить, т.к. индексы есть не по всем полям и условия могут быть с «пропусками».
Поменяем направление работы. Сделаем перезапись найденной номенклатуры.
ИначеЕсли СпособОбработки = "ОбработатьКоллекциюЗаписьНоменклатуры1ЗапросСФильтром" Тогда
ДлинаКода = Метаданные.Справочники.Номенклатура.ДлинаКода;
МассивКодов = Новый Массив;
Для каждого ЭлементКоллекции из Коллекция Цикл
ЭлементКоллекции.Значение = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
МассивКодов.Добавить(ЭлементКоллекции.Значение);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.Код ИЗ Справочник.Номенклатура КАК Номенклатура
|ГДЕ Номенклатура.Код В (&МассивКодов)";
Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Попытка
Выборка.Ссылка.ПолучитьОбъект().Записать();
Исключение
КонецПопытки;
КонецЦикла;
Функция ОбработатьСобытиеРазработчика(пПараметрыСобытия) Экспорт
ИмяСобытия = пПараметрыСобытия.ИмяСобытия;
СтруктураПараметров = пПараметрыСобытия.СтруктураПараметров;
РазрезМенеджеров = СтруктураПараметров.ПараметрыИнициализации.РазрезМенеджеров;
ОтветСобытия = Неопределено;
Если РазрезМенеджеров = "ОбработатьКоллекциюЗаписьНоменклатуры1ЗапросСФильтром" Тогда
Если ИмяСобытия = "ПриОбработкеДействияПотока" Тогда //Поток
ОтветСобытия = ОбработатьКоллекциюЗаписьНоменклатуры1ЗапросСФильтром_ПриОбработкеДействияПотока(пПараметрыСобытия);
КонецЕсли;
КонецЕсли;
Возврат ОтветСобытия;
КонецФункции
//******************************************************
Функция ОбработатьКоллекциюЗаписьНоменклатуры1ЗапросСФильтром_ПриОбработкеДействияПотока(пПараметрыСобытия)
Коллекция = пПараметрыСобытия.СтруктураДанных;
ДлинаКода = Метаданные.Справочники.Номенклатура.ДлинаКода;
МассивКодов = Новый Массив;
Для каждого ЭлементКоллекции из Коллекция Цикл
ЭлементКоллекции.Значение = Прав(("00000000000000000000"+Формат(ЭлементКоллекции.Значение,"ЧГ=")), ДлинаКода);
МассивКодов.Добавить(ЭлементКоллекции.Значение);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Номенклатура.Ссылка, Номенклатура.Код ИЗ Справочник.Номенклатура КАК Номенклатура
|ГДЕ Номенклатура.Код В (&МассивКодов)";
Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
ТЗ = Запрос.Выполнить().Выгрузить();
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Попытка
Выборка.Ссылка.ПолучитьОбъект().Записать();
Исключение
КонецПопытки;
КонецЦикла;
КонецФункции
Как можно заметить, распараллеливание не является «панацеей» и все зависит как от задачи, так и от способа реализации.
Всегда надо учитывать, что есть «накладные расходы».
Кроме того, тестирование необходимо делать на разных объемах данных, чтобы понять, как ведут себя алгоритмы (пропорционально увеличению или нет).
Данный способ обработки подразумевает следующую идею: «Основная программа» в цикле перебирает объекты требующие обработки и на каждой итерации цикла, объект(ы) с необходимыми параметрами отправляется в «Менеджер потоков», где по каждому объекту производиться расчет ресурсов и на основании этих ресурсов строится граф зависимости объектов (если ресурсы 2х и более объектов совпадают, данные объекты становятся связанными).
Ресурсы - это основной объект для построения связанного графа. О том как они формируются и как на их основе строится граф говорилось тут.
Здесь лишь приведу краткую схему:
Пример будет надуманный, но надеюсь понятный, так же в нем постараюсь показать и работу с ресурсами и пропуск объектов по условию. Оперировать будем все тем же списком с номерами, но перезаписывать номенклатуру будем по определенным правилам.
Условие «И» формирует 1 ресурс по комбинации значений вложенного массива. Порядок значений (типов данных) во вложенном массиве имеет строгое значение (описание: Формирование ресурсов).
Правила построения зависимости: По значению списка значений находим номенклатуру в справочнике по коду (как в предыдущих примерах), далее у найденной номенклатуры определяем «Тип номенклатуры» (Ссылка.ВидНоменклатуры.ТипНоменклатуры) и первую букву «Наименования». Та номенклатура, чей «Тип» И «Группа» букв (буквы разобьем на 6 групп по 5: а-д / е-и / й-н / о-т / у-ч / ш-я и прочие знаки, дабы сократить количество вариантов) будут совпадать обрабатываться будут друг за другом. Кроме того, если в перечень попадет «Группа» - данная номенклатура будет игнорироваться.
Так будет выглядеть граф для первых 30 позиций (Условие «И»), построен вручную на основе вышеизложенной таблицы.
Объект посылается в обработку только в том случае, если обработаны ВСЕ «Ведущие» объекты с которыми он связан своими ресурсами и если есть свободные потоки.
Сравним граф с результатами «Мониторинг порядка обработки объектов» и видим, что порядок каждой группы точно соответствует графу (цвета и группы добавлены вручную для ясности):
А вот так выглядит график «Мониторинг менеджера потоков», согласно заданных условий. Распараллелить, согласно заданных условий, получилось плохо. Выборка - 100 объектов.
А так выглядит тот же график при выборке (10 000):
Тот же график (10 000 объектов), но ближе:
Как мы видим, начало похоже на то, что было при выборке в 100 объектов, это и логично, порядок подачи объектов в обработку не изменился, но вот распараллеливания с 26 сек при прошлой выборе увидеть не удалось, т.к. выборка была мала.
Время возросло существенно, но в тоже время быстрее линейного способа обработки без поставленных условий.
Условие «ИЛИ» позволяет определить по объекту более одного ресурса и связать объекты по каждому из полученных ресурсов. Порядок ресурсов НЕ имеет значения.
Предпочтительно данное условие применять, если у объекта есть множество однотипных значений, например, табличная часть, хотя все зависит от задачи.
Правила построения зависимости: По значению списка значений находим номенклатуру в справочнике по коду (как в предыдущих примерах), далее у найденной номенклатуры определяем «Тип номенклатуры» (Ссылка.ВидНоменклатуры.ТипНоменклатуры) и первую букву «Наименования». Та номенклатура, чей «Тип» ИЛИ «Группа» букв (буквы разобьем на 6 групп по 5: а-д / е-и / й-н / о-т / у-ч / ш-я и прочие знаки, дабы сократить количество вариантов) будут совпадать обрабатываться будут друг за другом. Кроме того, если в перечень попадет «Группа» - данная номенклатура будет игнорироваться.
В принципе все тоже самое, что в примере 8, изменения коснуться только одного события разработчика «ПриПолученииРесурсов»
Если в примере №8 у каждого объекта один ресурс - комбинация («Группа» И «Тип») и связь между объектами так же одна, то в данном примере, у каждого объекта уже 2а ресурса: «Группа» ИЛИ «Тип». Связь строиться по каждому ресурсу до ближайшего (в обратном порядке поступления на обработку) объекта с таким же ресурсом.
Объект посылается в обработку только в том случае, если обработаны ВСЕ «Ведущие» объекты с которыми он связан своими ресурсами и если есть свободные потоки.
В данном случае пример получился не самый удачный, т.к. объекты друг за друга цепляются (получилось близкое к v1. Пример 4, а в идеале надо ресурсы собирать как в v1. Пример 5., тогда получиться добиться параллельности обработки).
Как и предполагали по построенной схеме, что 3 и 5 объекты обработаются одновременно, а все остальные пойдут по порядку.
Столь медленная обработка обусловлена рядом причин, основная из которых, это не возможность распараллелить выполнение при текущих данных и заданных условиях. Как это выглядит при других данных можно увидеть тут (пример 5), а так же из-за поэлементной передача данных на обработку в потоки.
Как можно заметить, распараллеливание не является «панацеей» и все зависит от конкретной задачи, способа реализации и объема данных.
Всегда надо учитывать, что при распараллеливании есть «накладные расходы».
Кроме того, не нужно забывать проводить тестирование на разных объемах данных, чтобы понять, как ведут себя алгоритмы (пропорционально увеличению или нет).
Менеджер потоков
Наименование | Файл | Версия | Размер | |||
---|---|---|---|---|---|---|
Многопоточность. Универсальный «Менеджер потоков» 2.0 + Интерактивная остановка менеджеров потоков + Презентация ISE 2018:
.zip 3,84Mb
12.02.19
63
|
.zip | 2.0.6 | 3,84Mb | 63 | Скачать |
Обработки
Наименование | Файл | Версия | Размер | |||
---|---|---|---|---|---|---|
Обработка с примерами из статьи
.epf 13,72Kb
12.02.19
30
|
.epf | 13,72Kb | 30 | Скачать | ||
Интерактивная остановка менеджеров потоков
.epf 10,67Kb
12.02.19
15
|
.epf | 10,67Kb | 15 | Скачать |
Все
Наименование | Файл | Версия | Размер | |||
---|---|---|---|---|---|---|
Презентация ISE 2018
.pptx 4,06Mb
12.02.19
0
|
.pptx | 4,06Mb | Скачать |
См. также
Специальные предложения
Суть то в том, чтобы запустить пачку заданий и при завершении хотя бы одного тут же выдать новое задание с новой порцией данных. Так можно максимально утилизировать процессор и управление потоками становится тривиальным
Для простоты понимания посмотрите
Скажите:
1. Применимо для восстановление партий + оценка стоимости по средней ?
2. Можно краткий пример как это все запустить для п1, в частности как и у вас для конфигурации УПП.
(33)
Спасибо за отзыв.
(33)
Применимо для любого учета, кроме разве что - РАУЗ (хотя может и там сработает не проверял).
Все процедуры восстановления - ТИПОВЫЕ с небольшими оговорками о них ниже...
Что у нас есть в типовом учете:
1. У нас есть обработка "Проведение по партиям". В ней процедура "КнопкаВыполнитьНажатие", которая в свою очередь, так или иначе, вызывает "ЗаполнениеДокументов.ВыполнитьВосстановление".
2. В рамках этой процедуры обрабатывается последовательность документов и каждый документ последовательности обрабатывается с помощью "УправлениеЗапасамиПартионныйУчет.ДвижениеПартийТоваров(..." (по крайне мере основная масса документов списания).
Все, что необходимо сделать - это чтобы эта строка кода выполнялась в событии "ПриОбработкеДействияПотока" со всеми своими параметрами
для этого необходимо следующее:
1. Перед началом цикла "Пока ЕстьДокументы Цикл" получить параметры менеджера потоков, установить параметры и провести его инициализацию
2. В цикле вместо "УправлениеЗапасамиПартионныйУчет.ДвижениеПартийТоваров (..." вызвать метод менеджера потоков "ОбработатьОбъект", куда параметрами передать а) СтруктуруПараметровМенеджера, б) ссылку на документ в) Структуру (или любую другой коллекцию) со всеми параметрами из типовой "УправлениеЗапасамиПартионныйУчет.ДвижениеПартийТоваров (..."
3. После цикла вызвать метод "ДождатьсяОстановкиМенеджераПотоков" куда передать только СтруктуруПараметровМенеджера.
Все больше в типовой обработке ни чего править не надо.
Дальше идет работа с событиями, что использовалось у Нас:
1. "ПередЗапускомМенеджераПотоков" Создали массив(пустую) для учета документов восстановление по которым не получилось;
2. "ПриПолученииРесурсов" Это основное событие распараллеливания - тут по каждому документу необходимо собирать его ресурсы. Как? Смотрите примеры из презентации или статьи
3. "ПриДобавленииВОчередьОбработки" - тут все просто, если ресурсы есть - возвращаем "Истина", если их нет - "Ложь". Кода возвращается "Истина", объект помещается в граф и по нему рассчитываются связи в зависимости от ресурсов (следующее событие "ПриОбработкеДействияПотока"), а если "Ложь", то объект - пропускается (следующее событие "ПриОбработкеПропуска")
4. "ПриОбработкеДействияПотока" собственно вызываем нашу строчку кода "УправлениеЗапасамиПартионныйУчет.ДвижениеПартийТоваров(..." со всеми параметрами. Результатом данного метода могут быть произвольные данные (ловятся в событии "ПриОбработкеОтвета")
5. Надо зафиксировать факт НЕ обработки объекта - это можно сделать в 2х событиях: "ПриОбработкеОшибки" - если поток падал в момент обработки объекта (по умолчанию до 5 раз пытается обработать объект) и в событии "ПриОбработкеОтвета" можно, например, при обработке события "ПриОбработкеДействияПотока" возвращать значение переменной "Отказ" если оно "Истина", в этом случае мы заносим объект в структуру определенную в п1.
6. "ПослеЗавершенияМенеджераПотоков" собственно - это последнее событие в нем мы производим установку границы последовательности. Если массив из п1 - пуст, то на самый последний документ последовательности в периоде, а если в массиве есть хоть один документ, то границу устанавливаем на самый ранний.
Для примера:
Количество строк кода во всех событиях: ~400
Самое большое событие: "ПриПолученииРесурсов" ~ 150 строк
Самое маленькое событие: "ПриДобавленииВОчередьОбработки" - 1 строка (Возврат пПараметрыСобытия.СтруктураДанных.МассивРесурсов.Количество() > 0;)
P.S. Один из не маловажных моментов :)
Код написанный в цикле процедуры "ВыполнитьВосстановление" - выполняется в транзакции, так вот: перед выполнением метода "ОбработатьОбъект" рекомендуется ее зафиксировать, а сразу за ним Начать новую. Кроме того в событии "ПриОбработкеДействияПотока" строку "УправлениеЗапасамиПартионныйУчет.ДвижениеПартийТоваров(..." - наоборот заключить в транзакцию
P.P.S. У нас помимо "УправлениеЗапасамиПартионныйУчет.ДвижениеПартийТоваров(..." в событии "ПриОбработкеДействияПотока" вызывается еще и "УправлениеПроизводствомДвиженияПоРегистрам.ПровестиДокументПоПроизводственнымРегистрам(..."
Но так у Нас, у Вас возможно будет по другому :)
Доброго дня.
А можно привести пример кода для формирования ресурса и как потом им пользоваться. Я в этом месте застрял.
Ресурсами ни как пользоваться не надо ими пользуется "менеджер потоков" главное правильно их сформировать (
Вот примерный код:
СтруктураДанных = пПараметрыСобытия.СтруктураДанных;
ОбрабатываемыйОбъект = СтруктураДанных.ОбрабатываемыйОбъект;
ПараметрыДляОбъекта = СтруктураДанных.ПараметрыДляОбъекта;
МассивРесурсов = Новый Массив;
ТаблицаСписания = УправлениеЗапасамиПартионныйУчет.ПолучитьТаблицуСтрокДокументов(ОбрабатываемыйОбъект,
ПараметрыДляОбъекта.Упр,
ПараметрыДляОбъекта.Бух,
ПараметрыДляОбъекта.Нал);
Для каждого СтрокаСписания из ТаблицаСписания Цикл
МассивРесурса = Новый Массив;
МассивРесурса.Добавить(СтрокаСписания.Номенклатура);
МассивРесурса.Добавить(СтрокаСписания.Склад);
МассивРесурсов.Добавить(МассивРесурса);
КонецЦикла;
Возврат МассивРесурсов;
Показать1. "ПередЗапускомМенеджераПотоков" Создали массив(пустую) для учета документов восстановление по которым не получилось;
2. "ПриПолученииРесурсов" Это основное событие распараллеливания - тут по каждому документу необходимо собирать его ресурсы. Как? Смотрите примеры из презентации или статьи
3. "ПриДобавленииВОчередьОбработки" - тут все просто, если ресурсы есть - возвращаем "Истина", если их нет - "Ложь". Кода возвращается "Истина", объект помещается в граф и по нему рассчитываются связи в зависимости от ресурсов (следующее событие "ПриОбработкеДействияПотока"), а если "Ложь", то объект - пропускается (следующее событие "ПриОбработкеПропуска")
4. "ПриОбработкеДействияПотока" собственно вызываем нашу строчку кода "УправлениеЗапасамиПартионныйУчет.ДвижениеПартийТоваров(..." со всеми параметрами. Результатом данного метода могут быть произвольные данные (ловятся в событии "ПриОбработкеОтвета")
5. Надо зафиксировать факт НЕ обработки объекта - это можно сделать в 2х событиях: "ПриОбработкеОшибки" - если поток падал в момент обработки объекта (по умолчанию до 5 раз пытается обработать объект) и в событии "ПриОбработкеОтвета" можно, например, при обработке события "ПриОбработкеДействияПотока" возвращать значение переменной "Отказ" если оно "Истина", в этом случае мы заносим объект в структуру определенную в п1.
6. "ПослеЗавершенияМенеджераПотоков" собственно - это последнее событие в нем мы производим установку границы последовательности. Если массив из п1 - пуст, то на самый последний документ последовательности в периоде, а если в массиве есть хоть один документ, то границу устанавливаем на самый ранний.
Дошел до событий, дабы не городить костыли - можно пример кода по каждому из событий для восст. по партиям?
В рамках данной статьи это не предусмотрено.
Постараюсь собраться с силами и выложить новую со всеми кодами для восстановления партий на нашем примере :)
Вам могу, пока, посоветовать "городить костыли" заодно опробуете все сами.
Рекомендации:
0) Поднимите копию базы и сделайте там первое полное восстановление за месяц (это будет эталон для сравнения)
1) разработку рекомендую начать в отдельной базе с 1 потока;
2) восстановление делайте в рамках 1 дня;
3) для сверки пользуйтесь "Ведомость по партиям товаров на складах"
4) как добьетесь результата на 1 потоке увеличивайте их количество до 2-3, если и на них все будет гладко, то ставьте, то количество которое потянет ваш сервер :) (У нас восстановление делается на 10 потоках - этого для нас достаточно. Но была задача по непосредственному удалению документов из БД, там писали обработку и удаляли на 45 потоках. Все зависит от специфики, задачи, железа и т.д.)
Для восстановление выше приведенного кода(с расчетом ресурсов) не достаточно - все зависит от специфики Вашего предприятия. В нашем дополнительно еще собираются ресурсы по производственной части.
Так что, дерзайте!
P.S. Я надеюсь у Вас управляемый режим блокировок?
Запускаю потоки через "мпМенеджерПотоков.ОбработатьКоллекциюОбъектов".
Рекомендую посмотреть презентацию с IE2018 (ссылка есть в публикации) или
Необходимый минимум для метода "ОбработатьКоллекциюОбъектов" показан
Пока прикладываю схему доступности событий от методов
Подскажите что я делаю не так:
1. Получаю параметры "ПолучитьСтруктуруПараметровИнициализацииМенеджераПотоков"
2. Инициализирую МП "ИнициализироватьМенеджерПотоков"
3. В цикле обхожу выборку документов методом "ОбработатьОбъект"
4. После цикла ожидаю завершения МП "ДождатьсяОстановкиМенеджераПотоков"
Получаю ошибку:
Поле объекта не обнаружено (АлгоритмыРаботыСКоллекцией)
КодСоздания = пСтруктураПараметров.ПараметрыИнициализации.ПараметрыОбработкиКоллекции.АлгоритмыРаботыСКоллекцией.КодСоздания;
В структуре "ПараметрыОбработкиКоллекции" действительно нет "АлгоритмыРаботыСКоллекцией".
Перед циклом дописал:
КодСоздания = "КоллекцияПриемник = Новый СписокЗначений;";
КодДобавления = "ЗаполнитьЗначенияСвойств(КоллекцияПриемник.Добавить(), КоллекцияИсточник[ИндексКоллекции]);";
Структура = Новый Структура;
Структура.Вставить("КодСоздания" , КодСоздания);
Структура.Вставить("КодДобавления", КодДобавления);
СтруктураПараметромМП.ПараметрыИнициализации.ПараметрыОбработкиКоллекции.Вставить("АлгоритмыРаботыСКоллекцией", Структура);
А как нужно было сделать правильно?
Но вот код вызывающий у Вас ошибку срабатывать не должен. Он относится к методам работы с коллекциями.
И встречается в 2х местах:
Функция ПолучитьДанныеИзМенеджераПотоков
...
ИначеЕсли РезультатОбработки = "ДополненныйФрагментКоллекции" Тогда
...
и
Функция ОбработатьКоллекцию
которая в свою очередь вызывается из метода "ОбработатьКоллекциюОбъектов" и "ДополнитьКоллекциюОбъектов"
Версия менеджера у Вас 2.0.5? (не могу определить дату скачивания - не вижу Вас среди скачавших)
Можете мне прислать замер производительности по работе ФЗ "менеджера потоков"? (например через вотсап - номер в профиле)
и Код используемой обработки или саму обработку.
Сообщить("Обработка в потоках.");
ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
СтруктураПараметров = векМенеджерПотоков.ПолучитьСтруктуруПараметровИнициализацииМенеджераПотоков("ОбработатьКоллекциюДокументовОчередипереобеспечения","");
векМодульМенеджераПотоков.ПолучитьПараметрыМП("ЗаказПоставщику",СтруктураПараметров);
СтруктураПараметромМП = векМенеджерПотоков.ИнициализироватьМенеджерПотоков(СтруктураПараметров);
#Область Костыли
КодСоздания = "КоллекцияПриемник = Новый СписокЗначений;";
КодДобавления = "ЗаполнитьЗначенияСвойств(КоллекцияПриемник.Добавить(), КоллекцияИсточник[ИндексКоллекции]);";
Структура = Новый Структура;
Структура.Вставить("КодСоздания" , КодСоздания);
Структура.Вставить("КодДобавления", КодДобавления);
СтруктураПараметромМП.ПараметрыИнициализации.ПараметрыОбработкиКоллекции.Вставить("АлгоритмыРаботыСКоллекцией", Структура);
#КонецОбласти
Для Каждого ДокументСсылка Из Коллекция Цикл
СтруктурапараметровОбъекта = Новый Структура;
СтруктурапараметровОбъекта.Вставить("Период",ДокументСсылка.Период);
векМенеджерПотоков.ОбработатьОбъект(СтруктураПараметромМП,ДокументСсылка.Документ,СтруктурапараметровОбъекта);
КонецЦикла;
векМенеджерПотоков.ДождатьсяОстановкиМенеджераПотоков(СтруктураПараметромМП);
ВремяКонцаВыполнения = ТекущаяУниверсальнаяДатаВМиллисекундах();
ВремяВыполненияВМиллисекундах = ВремяКонцаВыполнения - ВремяНачала;
Сообщить("Окончание потоковой обработки: "+ВремяВыполненияВМиллисекундах / 1000);
ПоказатьПроцедура ПолучитьПараметрыМП(ОбъектОбработки,СтруктураПараметров) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| векНастройкиМногопоточности.МодульОбработкиСобытийРазработчика КАК МодульОбработкиСобытийРазработчика,
| векНастройкиМногопоточности.МинимальноеКоличествоКоллекции КАК МинимальноеКоличествоКоллекции,
| векНастройкиМногопоточности.КоличествоПотоков КАК КоличествоПотоков,
| векНастройкиМногопоточности.КоличествоЭлементовКолекцииНаПоток КАК КоличествоЭлементовКолекцииНаПоток,
| векНастройкиМногопоточности.КоэффициентКратностиОчередиПотоковКПотокам КАК КоэффициентКратностиОчередиПотоковКПотокам,
| векНастройкиМногопоточности.ПределКоличествоПопытокОбработатьОбъект КАК ПределКоличествоПопытокОбработатьОбъект
|ИЗ
| РегистрСведений.векНастройкиМногопоточности КАК векНастройкиМногопоточности
|ГДЕ
| векНастройкиМногопоточности.Активно
| И векНастройкиМногопоточности.ОбъектОбработки = &ОбъектОбработки";
Запрос.УстановитьПараметр("ОбъектОбработки", ОбъектОбработки);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
СтруктураПараметров.ПараметрыОбщие.Вставить("Активно",Ложь);
Если Выборка.Следующий() Тогда
Если ЗначениеЗаполнено(Выборка.МодульОбработкиСобытийРазработчика) Тогда
СтруктураПараметров.ПараметрыИнициализации.МодульОбработкиСобытийРазработчика = Выборка.МодульОбработкиСобытийРазработчика;
КонецЕсли;
Если ЗначениеЗаполнено(Выборка.КоличествоПотоков) Тогда
Если ТипЗнч(Выборка.КоличествоПотоков) = Тип("Число") Тогда
СтруктураПараметров.ПараметрыИнициализации.КоличествоПотоков = Выборка.КоличествоПотоков;
КонецЕсли;
Если ТипЗнч(Выборка.КоличествоПотоков) = Тип("Булево") Тогда
СтруктураПараметров.ПараметрыИнициализации.ПараметрыОбработкиКоллекции.ДинамическийРассчетКоличестваПотоков = Выборка.КоличествоПотоков;
КонецЕсли;
КонецЕсли;
Если ЗначениеЗаполнено(Выборка.КоличествоЭлементовКолекцииНаПоток) Тогда
СтруктураПараметров.ПараметрыИнициализации.ПараметрыОбработкиКоллекции.КоличествоЭлементовКолекцииНаПоток = Выборка.КоличествоЭлементовКолекцииНаПоток;
КонецЕсли;
Если ЗначениеЗаполнено(Выборка.КоэффициентКратностиОчередиПотоковКПотокам) Тогда
СтруктураПараметров.ПараметрыИнициализации.КоэффициентКратностиОчередиПотоковКПотокам = Выборка.КоэффициентКратностиОчередиПотоковКПотокам;
КонецЕсли;
Если ЗначениеЗаполнено(Выборка.ПределКоличествоПопытокОбработатьОбъект) Тогда
СтруктураПараметров.ПараметрыИнициализации.ПределКоличествоПопытокОбработатьОбъект = Выборка.ПределКоличествоПопытокОбработатьОбъект;
КонецЕсли;
СтруктураПараметров.ПараметрыОбщие.Активно = Истина;
СтруктураПараметров.ПараметрыОбщие.Вставить("МинимальноеКоличествоКоллекции",Макс(Выборка.МинимальноеКоличествоКоллекции,1));
КонецЕсли;
КонецПроцедуры
ПоказатьЕсли ИмяСобытия = "ПриПолученииРесурсов" Тогда //Поток
ОтветСобытия = ПриПолученииРесурсов(пПараметрыСобытия);
//Сообщение = Новый СообщениеПользователю;
//Сообщение.Текст = "Ресурс получен";
//Сообщение.Сообщить();
ИначеЕсли ИмяСобытия = "ПриОбработкеДействияПотока" Тогда //Поток
СтруктураДанных = пПараметрыСобытия.СтруктураДанных;
ПараметрыДляОбъекта = СтруктураДанных.ПараметрыДляОбъекта;
РегистрыСведений.векОчередьДляПереобеспечения.ОбработатьДокумент(ПараметрыДляОбъекта.Период, СтруктураДанных.ОбрабатываемыйОбъект);
ПоказатьФункция ПриПолученииРесурсов(пПараметрыСобытия)
СтруктураДанных = пПараметрыСобытия.СтруктураДанных;
ОбрабатываемыйОбъект = СтруктураДанных.ОбрабатываемыйОбъект;
ТабличнаяЧастьДокумента = ПолучитьТабличнуюЧастьДокумента(ОбрабатываемыйОбъект);
МассивРесурсов = Новый Массив;
МассивРесурса = Новый Массив;
МассивРесурса.Добавить(векПолучитьЗначениеРеквизита(ОбрабатываемыйОбъект,"Склад"));
Для каждого СтрокаТЧД из ТабличнаяЧастьДокумента Цикл
МассивРесурса.Добавить(СтрокаТЧД.Номенклатура);
КонецЦикла;
МассивРесурсов.Добавить(МассивРесурса);
Возврат МассивРесурсов;
КонецФункции
Показать
Просмотры 58796
Загрузки 98
Комментарии 42
Создание 07.02.18 16:13
Обновление 12.02.19 16:17
№ Публикации 778905
Рубрики
Оптимизация БД (HighLoad),
Инструментарий,
Теория программирования,
Универсальные функции
Кому Программист
Тип файла Конфигурация (md, cf)
Платформа Платформа 1С v8.x (все механизмы)
Конфигурация Конфигурации 1cv8
Операционная система Не имеет значения
Страна Россия
Отрасль Не имеет значения
Налоги Не имеет значения
Вид учета Не имеет значения
Раздел учета Не имеет значения
Доступ к файлу Абонемент ($m)
Код открыт Да
|

