Пролог.
Т.к. тема вызвала значительный интерес, хотелось бы дополнить ее ссылкой на раздел сайта Intel для разработчиков. В частности там говорится следующее:
Когда-то параллельное программирование было уделом только тех одиночек, которых интересовали задачи для огромных суперкомпьютеров. Но теперь, когда на многоядерных процессорах начали работать обычные приложения, параллельное программирование быстро становится технологией, которую должен освоить и уметь применять любой профессиональный разработчик ПО.
Введение.
Итак, многопоточность - это возможность выполнять несколько программ (потоков, threads) одновременно. Клиент-серверная архитектура 1С позволяет реализовать данную функцию через механизм фоновых заданий, при этом можно добиться существенного повышения производительности решений на платформе 1С.
Многопроцессорные сервера 1С являются устоявшимся фактом. Загрузка процессоров на серверах даже при достаточно интенсивной работе пользователей не редко держится на уровне 20-30%, т.к. клиентская часть 1С берет на себя функционал как минимум по отображению форм и, возможно, какую-то дополнительную нагрузку. В толстом же клиенте нагрузка на нее еще выше. Отсюда следует, что на серверах 1С существует неиспользованный вычислительный ресурс, который можно утилизировать.
Как это работает?
Для того, чтобы заставить 1С что-то делать параллельно, нужно обрабатываемые данные разбить на куски и постепенно скармливать создаваемым для этого потокам. Архитектурно это состоит из механизма получения порции данных - источника данных, механизма обработки данных - рабочей процедуре, и управляющей функции.
В итоге у нас вырисовывается такая схема:
Управляющая процедура -> Поставщик данных (запрос) -> Фоновое задание (порция данных) -> Отчет о выполнении -> Управляющая процедура.
Математическая модель будет выглядеть так:
- Получаем данные для обработки.
- Задаем количество фоновых заданий.
- Создаем массив для хранения фоновых заданий.
- Если количество стартованных фоновых заданий меньше, чем задано - п.5., иначе п.7.
- Стартуем фоновое задание и передаем ему оговоренное количество данных.
- Еще есть данные? Да - п.4, нет - выход (или подождем через ОжидатьЗавершения(МассивФЗ), после чего выйдем).
- Проверяем, есть ли в массиве задание, которое уже отработало. Если есть - п.5., иначе п.7.
Практическая задача.
Допустим, нам надо заполнить новый реквизит регистра сведений в соответствии с определенными условиями, которые могут быть отбором компоновки данных.
Дано:
- Регистр сведений "СферическиеКони" с измерением "Конь" (справочник "Кони") и ресурсом "ПлотностьСреды" (число 15,10) .
- Мы добавили параметр "СопротивлениеСреды", характеризующий сопротивление среды при максимальной скорости данного коня. Именно его и надо заполнить.
Предположим, что коней у нас много и заполнение без многопоточности займет много времени.
Опишем для начала рабочую процедуру:
Процедура ЗаполнитьСопротивлениеСреды(ИсточникДанных) Экспорт
Для Каждого Ст ИЗ ИсточникДанных Цикл
Рег = РегистрыСведений.СферическиеКони.СоздатьМенеджерЗаписи();
Рег.Конь = Ст.Конь;
Рег.Прочитать();
Рег.СопротивлениеСреды = ПолучитьСопротивлениеСредыДляКоня(Ст.Конь.МаксСкорость, Ст.Конь.Радиус, Рег.ПлотностьСреды);
Рег.Записать();
КонецЦикла;
КонецПроцедуры
Данная процедура должна располагаться в общем модуле и быть экспортной, т.к. именно ее будет вызывать управляющая процедура, формируя фоновые задания.
Теперь опишем управляющую процедуру:
В принципе, управляющая процедура может и не быть процедурой - она может продолжить код получения данных
МассивФЗ = Новый Массив;
КоличествоЗаданий = 10;
КоличествоДанныхДляЗадания = 100;
МассивДанныхЗадания = Новый Массив;
Для Каждого Ст ИЗ ИсточникДанных Цикл
МассивДанныхЗадания.Добавить(Ст);
Если МассивДанныхЗадания.Количество() = КоличествоДанныхДляЗадания Тогда
Пока МассивФЗ.Количество() = КоличествоЗаданий Цикл
Для Каждого ФЗ ИЗ МассивФЗ Цикл
// не помню, как получается задание, но как-то так - нет под рукой 1С
// смысл в том, чтобы проверить статус у запущенного задания, и если он не активен - освободить место в массиве ФЗ
Если НЕ ФоновыеЗадания.ПолучитьФоновоеЗаданиеПоГуид(ФЗ).Статус = СтатусыФЗ.Активно Тогда
МассивФЗ.Удалить(МассивФЗ.Найти(ФЗ));
Прервать; // выйдем из "бесконечного" цикла, когда хотябы одно из заданий закончилось
КонецЕсли;
КонецЦикла; // ФЗ
КонецЦикла; // КоличествоФЗ
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(МассивДанныхЗадания);
ФЗ = ФоновыеЗадания.Выполнить("ОбщийМодуль.ЗаполнитьСопротивлениеСреды", МассивПараметров);
МассивФЗ.Добавить(ФЗ.ГУИД);
МассивДанныхЗадания = Новый Массив;
КонецЕсли; // КоличествоДанныхДляЗадания
КонецЦикла;
Если МассивДанныхЗадания.Количество() > 0 Тогда
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(МассивДанныхЗадания);
ФЗ = ФоновыеЗадания.Выполнить("ОбщийМодуль.ЗаполнитьСопротивлениеСреды", МассивПараметров);
КонецЕсли;
Собственно, что мы сделали? Создали массив заданий и определили параметры обработки (количество потоков, количество данных в потоке). Далее заполнили массив данными, проверили, есть ли свободное место в массиве, если нет - дождались, и передали в фоновое задание для обработки. После чего поместили ГУИД ФЗ в массив.
Заключение.
Многопоточные вычисления все больше будут использоваться в дальнейшем, т.к. процессорная архитектура достигла пределов роста вверх по частоте - теперь она растет вширь по количеству ядер, объему памяти, емкости накопителей. Все это должно привести к прогрессу в области параллельных вычислений.