Добрый день, коллеги.
Небольшая статейка и готовое решение для внедрения в свою конфигурацию.
Введение
Вот, сколько я проработал и не разу всерьез не задумывался о многопоточности, пока не начал работу над проектом "Классификатор адресов Украины". При работе в рамках этого проекта столкнулся с необходимость загрузки большого объема данных в конфигурацию, а точнее - необходимо было создать множество элементов в разных справочниках, счет шел на 10-ки а то и 100-ни тысяч элементов...
И как бы меня стало очень сильно "напрягать" продолжительная загрузка данных в рамках тестирования. Ну, что же теперь? Решил поискать в "интернетах", и к сожалению, нашел только старые статьи(но по большому счету ничего с тех времен не изменилось). Да и готового, качественного решения не нашел, пришлось писать самому. И к моему удивлению, скорость загрузки я смог увеличить в 20 раз, благодаря элементарному коду... Какой же глупый я был)
НО ускорения в 20 раз я добился только на сервере, на своей печатной машинке, ускорение было всего 2 раза, при использовании 2 потоков, использовать больше не рекомендую, т.к. особого эффекта от этого не будет. Результат тестов прилагаю.
Ну, как говорится, "погнали".
Внедрение
1. Создаем общий модуль(серверный), называем его "Многопоточность".
2. Копируем в него ниже изложенный код:
#Область ПрограммныйИнтерфейс
// Запускается выполнение указанной процедуры в много поточном режиме
//
// Параметры:
// ИмяПроцедуры - Строка - имя экспортной процедуры общего модуля или модуля менеджера объекта,
// которую необходимо выполнить в фоне (ЭкспортнаяПроцедура).
// Например, "МойОбщийМодуль.МояПроцедура", "Документы.МойДокумент.МояПроцедура".
// У процедуры должен быть один формальный параметр:
// * ПараметрыЭкспортнойПроцедуры - Произвольный - произвольные параметры ПараметрыЭкспортнойПроцедуры;
// СписокДанных - Массив - массив данных, которые будут разделены на части и переданы процедуру,
// которая указа в параметре "ИмяПроцедуры".
// КоличествоПотоков - Число - количество потоков на которое необходимо разделить обработку СпискаДанных,
// значение по умолчанию равно 2.
// Ожидание - Булево - признак указывающий на необходимость ожидания выполнения всех фоновых заданий,
// Истина - ожидать пока выполнятся все задания и потом возвращать управление в вызываемый код,
// Ложь - возвращать управление в вызываемый код сразу после создания всех заданий.
//
// Возвращаемое значение:
// <Отсутствует> -
//
// Варианты вызова:
// Запустить(ИмяПроцедуры, СписокДанных) -
//
Функция Запустить(Знач ИмяПроцедуры, Знач СписокДанных, Знач КоличествоПотоков = Неопределено, Знач Ожидание = Истина) Экспорт
// Отладка.
//ИмяПроцедуры = "";
//СписокДанных = Новый Массив;
// Расчеты основных параметров для распределения потоков.
КоличествоДанных = СписокДанных.Количество();
КоличествоПотоков = РассчитатьКоличествоПотоков(КоличествоДанных, КоличествоПотоков);
РазмерПорции = РассчитатьРазмерПорции(КоличествоДанных, КоличествоПотоков);
//
СписокФоновыхЗаданий = Новый Массив;
// Создание порций данных и запуск выполнения в фоне "ЭкспортнаяПроцедура".
Данные = Новый Массив;
Для Каждого Элемент Из СписокДанных Цикл
// Добавление данных в порцию.
Данные.Добавить(Элемент);
// Проверка размера порции и при достижении рассчитаного размера,
// запуск выполнения в фоне "ЭкспортнаяПроцедура".
Если Данные.Количество() = РазмерПорции Тогда
ЗапуститьВФоне(ИмяПроцедуры, Данные, СписокФоновыхЗаданий);
КонецЕсли;
КонецЦикла;
// Проверка остатка данных,
// при необходимости выполнение "ЭкспортнаяПроцедура" с остатком данных,
// количество которых меньше рассчитаного размера порции.
Если Данные.Количество() > 0 Тогда
ЗапуститьВФоне(ИмяПроцедуры, Данные, СписокФоновыхЗаданий);
КонецЕсли;
// Ожидание при необходимости и задержка передачи управления в вызываемый код.
Если Ожидание Тогда
ФоновыеЗадания.ОжидатьЗавершенияВыполнения(СписокФоновыхЗаданий);
КонецЕсли;
КонецФункции // Запустить()
#КонецОбласти
#Область СлужебныйПрограммныйИнтерфейс
Функция ЗапуститьВФоне(ИмяПроцедуры, СписокДанных, СписокФоновыхЗаданий)
Параметры = Новый Массив;
Параметры.Добавить(СписокДанных);
СписокФоновыхЗаданий.Добавить(
ФоновыеЗадания.Выполнить(ИмяПроцедуры, Параметры));
СписокДанных.Очистить();
КонецФункции // ЗапуститьВФоне()
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
Функция РассчитатьРазмерПорции(КоличествоДанных, КоличествоПотоков)
Возврат Цел(КоличествоДанных / КоличествоПотоков);
КонецФункции // РассчитатьРазмерПорции()
Функция РассчитатьКоличествоПотоков(КоличествоДанных, КоличествоПотоков)
Возврат ?(КоличествоПотоков <> Неопределено, КоличествоПотоков, 2)
КонецФункции // РассчитатьКоличествоПотоков()
#КонецОбласти
В коде прописал комментарии, так, что думаю, при внимательном чтении проблем возникнуть не должно.
3. Начинаем наслаждаться и использовать.
Использование
Теперь в любом серверном методе вам необходимо всего лишь вызвать функцию Запустить нашего созданного общего модуля Многопоточность.
Пример вызова:
Многопоточность.Запустить("КлассификаторАдресовУкраины.ЗагрузитьРайоныКОАТУУ", СписокРайонов, 10);
где:
- КлассификаторАдресовУкраины - это общий модуль(серверный)
- ЗагрузитьРайоныКОАТУУ - это экспортная процедура модуля КлассификаторАдресовУкраины
- СписокРайонов - массив данных, а в массиве у меня лично элементы с типом - Структура
- 10 - количество потоков.
Пример реализации процедуры, которою будем вызывать:
Процедура ЗагрузитьРегионыКОАТУУ(СписокДанных) Экспорт
Для Каждого Элемент Из СписокДанных Цикл
// траляля
КонецЦикла;
КонецПроцедуры // ЗагрузитьРегионыКОАТУУ
В этой процедуре в параметр попадает часть(кусок) данных из тех данных которые мы передали в функцию Многопоточность.Запустить()
Заключение
Вот собственно и все, надеюсь, все доступно объяснил, у кого есть предложения по улучшению - предлагайте. Буду рад!)
З.Ы. Использую функции, а не процедуры, т.к. планирую что-то доработать, а что, еще не придумал)))