Интервью

Параллельные вычисления в ТУРБО проще всего сравнить с языком Go


Борис Бабань
создатель и технический директор ТУРБО

Архитектура и инструментарий тиражных решений требуют постоянного развития

вместе с индустрией программного обеспечения. Успешный пример такой модернизации – реализация параллельных вычислений в ТУРБО, российской платформе разработки тиражных бизнес-решений. О параллельных вычислениях, ТУРБО и языке Go мы поговорили с Борисом Бабанем, создателем и техническим директором ТУРБО, Консист Бизнес Групп.



Где сегодня в информационных системах проявляются параллельные вычисления?


Прежде всего, это многопользовательская работа. ТУРБО – платформа для разработки учетных и управленческих информационных систем, и все приложения на этой платформе – многопользовательские. Множество пользователей подключаются к серверам приложений ТУРБО и работают одновременно. Каждый пользователь работает в своей сессии параллельно с другими, и система поддерживает это еще с 90-х годов.


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

Понимая, что пользователи все чаще сталкиваются с задачами обработки больших массивов данных, в последних релизах платформы ТУРБО мы реализо-вали масштабируемое параллельное выполнение заданий в рамках одной пользовательской сессии.

Теперь на уровне настройки серверного окружения диспетчеру параллельных вычислений можно указать любое количество располагаемых серверов, которые будут использоваться для исполнения параллельных вычислений.



Какое отражение в ТУРБО находят лучшие мировые практики применения подобных подходов?


При проектировании платформы ТУРБО мы обсуждаем сложившуюся практику и решения, применяемые в других языках и интегрированных средах разработки. Наверное, параллельные вычисления в ТУРБО проще всего сравнить с языком Go. Этот язык в свое время создала компания Google для реализации своих веб-приложений. У любого веб-приложения или сервиса есть пользовательский веб-интерфейс, база данных, а между ними – связующая серверная логика, которую удобно писать на Go.


В Go реализуется множество параллельных процессов, которые обрабатывают многочисленные запросы от веб-интерфейса. При этом параллельные процессы в вебе и в бизнес-приложениях – разные, и этим обусловлены многочисленные различия на уровне кода и на уровне исполнения параллельных процессов. Важно, что процессы в веб-приложениях Go простые и короткие, и эти процессы работают в одном адресном пространстве – с единым набором переменных и единым окружением.


В бизнес-приложениях на платформе ТУРБО процессы другие. Они достаточно сложные, и могут требовать изменения большого количества записей в разных таблицах базы данных. Процессы подразумевают некоторое окружение, контекст, соединение с базой данных, исполнение в виде транзакций. Поэтому параллельные процессы ТУРБО работают в разных адресных пространствах.



В чем заключаются основные принципы параллельных вычислений в Go?


Если в коде Go просто написать имя функции типа Func(), то это будет просто последовательный вызов функции. После завершения исполнения этой функции управление вернется в вызывающую программу. Но если перед вызовом функции написать оператор go, типа go Func(), то функция вызовется асинхронно, то есть управление в вызывающую программу вернется сразу, а процедура будет выполняться в отдельном процессе.


Так как в Go параллельные процессы работают в едином адресном пространстве, возникает важная задача синхронизации этих процессов, чтобы они не мешали друг другу при работе в одном окружении с одними и теми же переменными. Главный инструмент синхронизации в Go – так называемые каналы channels. В языке Go это встроенный тип данных, который представляет из себя очередь. Один процесс в такой канал помещает элементы данных, а другой процесс эти элементы выбирает. Таким образом от одного процесса мы доставляем элементы данных другому. Если сделать канал ограниченной длины, например, длины единицы, то такой канал можно использовать как средство синхронизации параллельных процессов.


Например, stopChan := make (chan os.Signal) , где переменная stopChan – это канал. Встроенная функция make создает канал типа os.Signal, а в основном процессе мы пытаемся выбрать сообщение из него. Если в канале ничего нет, исполнение основного процесса прервется. Он будет просто ждать, пока в канал что-нибудь поступит, а параллельные процессы при этом будут работать.


Таким образом, исполнение программы на Go представляет из себя набор параллельных процессов, которые работают в одном адресном пространстве. Каждый из процессов выполняется как бы монопольно до тех пор, пока в нем не встретится обращение к каналу. Как только такое обращение встретится, выполнение может остановиться, что вызовет передачу управления на другой процесс.


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



Чем тогда концептуально отличаются параллельные процессы в ТУРБО и Go?


Параллельные процессы в ТУРБО устроены по-другому – в отличие от Go у нас нет процессов, которые работают параллельно в одном адресном пространстве. В программах на языке ТУРБО Скрипт код каждого пользователя выполняется в отдельном процессе, из которого можно запускать другие процессы, параллельные исходному. Эти процессы похожи на запуск сессии для нового пользователя, но исполняются программно.


Важно, что каждый такой процесс работает в своём адресном пространстве. То есть у него есть свое окружение и свой набор переменных, которые создаются заново, в отличие от языка Go. Этот процесс, как и в Go, может синхронизироваться и обмениваться данными с другими процессами. Для этого в ТУРБО мы реализовали свои каналы, которые у нас называются pipe.

Напомню, в Go канал channel – это тип данных, встроенный в язык. У нас в ТУРБО канал pipe – это один из библиотечных классов, что, с одной стороны, требует явного создания объектов такого типа перед началом их использования, с другой – позволяет легко расширять набор методов и наращивать возможности работы с каналами. Ключевые методы для каналов – это put() и get(), запись и выбор значения из канала.


Конструкция имя_пайпа.get() в коде ТУРБО ожидает появления значения в этом канале. Основной процесс будет просто стоять и ждать, пока кто-то не положит в этот pipe значение. Значение туда должен положить какой-то другой процесс. Процессы работают параллельно, а завершение исполнения мы распознаем с помощью put() и get(). Механика работы в целом похожа на язык Go.



Мы поговорили про логику разработки. Какова механика исполнения этого кода на серверах?


Если вычислительные задания работают истинно параллельно, то их исполнение занимает достаточно много системных ресурсов. Директивный запуск 1000 параллельных процессов создает большую нагрузку на сервер. Для оптимизации загрузки системных ресурсов используется так называемая исполнительная система. Так, если в Go запустить 1000 параллельных заданий, то там исполнительная система Go сама разберется с их загрузкой на сервера.

В ТУРБО мы также реализовали исполнительную систему и диспетчер заданий, которые снимают зависимость разработки от вычислительной мощности серверного окружения. 1000 квазипараллельно работающих заданий запустить можно, но при этом реально они будут работать не истинно параллельно, а встанут в очередь и начнут из этой очереди последовательно выбираться и исполняться. При этом они не займут все системные ресурсы и не перегрузят исполняемую систему с исчерпанием памяти или другими проблемами.



Что общего у исполнительной системы ТУРБО и Go?

Общее то, что я как программист-разработчик не забочусь о том, какое оборудование будет выполнять эту функцию, могу запустить 1000 процессов, и исполнительная система Go обеспечит выполнение всех этих процессов. Если у меня один компьютер, все эти процессы будут выполняться квазипараллельно, то есть просто встанут в очередь из 1000 процессов. Как только один из процессов остановится, например, на чтении канала, начнет выполняться следующий процесс.


На мощном оборудовании, например, на многопроцессорном сервере или нескольких серверах, процессы распараллеливаются. Разработчик вообще не знает, как именно Go организует исполнителей для этих процессов, которые работают не квазипараллельно, а уже по-настоящему параллельно.

Аналогом оператора go в ТУРБО является конструкция job. При запуске job разработчик не знает, на каком сервере это будет выполняться. Это отдается на откуп диспетчеру этих исполнителей, который занимается балансировкой нагрузки. ТУРБО реализует концепцию именно пула потоков, когда есть обработчики, которые по очереди выбирают задания. Моменты запуска параллельных процессов и выделения ресурсов под эти процессы разделены. Ресурсы выделяет диспетчер, а процессы запускает пользовательская программа.



Как именно параллельные вычисления используются в ТУРБО?


Конструкции параллельных вычислений – уже названные задания job, а также каналы pipe используются в решении ряда базовых задач платформы ТУРБО. В частности, с помощью этих возможностей реализовано параллельное удаление записей, перепроектируются нумераторы, реализована печать на веб-клиенте, реализуется балансировочная система параллельных вычислений.


Простейший случай использования каналов ТУРБО – это так называемые семафоры. Они нужны для того, чтобы гарантировать работу какой-то критической секции кода в однопользовательском режиме. В качестве примера можно привести формирование уникальных номеров для записей документов и событий в прикладных решениях ТУРБО. В библиотеке функций ТУРБО есть функции CreateSemaphore, LockSemaphore и UnlockSemaphore. Функция CreateSemaphore создает семафор – канал единичной длины. Он создается один раз, например, при инициализации программы.



При входе в критическую секцию кода мы выполняем LockSemaphore, в которой помещаем в канал-семафор пустое значение aSem.Put(nil). Длина этого канала-семафора равна единице. Кладем какое-то значение в этот канал. Если в канале ничего нет, то значение туда ляжет, и программа успешно войдет в критическую секцию. Пока программа не вышла из критической секции – другой процесс зайти в критическую секцию не сможет. Другой процесс попробует выполнить Lock и увидит, что канал-семафор заполнен: размер его равен единице и в нем уже есть значение. Этот другой процесс будет просто ждать, пока мы не освободим критическую секцию, пока не выполним в ней функцию UnlockSemaphore. При выходе из критической секции программа забирает значение из канала. Таким образом, критическая секция кода выполняется строго в однопользовательском режиме, ожидающие процессы выстраиваются в очередь.



Есть примеры использования подобных конструкций в прикладных задачах?


Основной путь применения заданий job в прикладных задачах – разделить сложное вычислительное задание на части, дождаться завершения выполнения всех частей и продолжить выполнение основного процесса. Здесь каналы используются для синхронизации вычислительных заданий.


Если мы захотим посчитать амортизацию на 1 млн основных средств, то мы делим эти основные средства на группы, а потом расчет амортизации каждой группы запускаем отдельным заданием job. Запускаем множество этих job, а в конце ставим вызов Job.WaitAll(), параметром которого является массив этих job. Задания выполняются параллельно, и после завершения можно обработать их общий результат.



Куда дальше будет двигаться развитие параллельных вычислений в ТУРБО?


В разработке ТУРБО мы всегда отталкиваемся от практических задач. Как только у нас появятся новые задачи, которые потребуют параллельных вычислений в едином адресном пространстве, как в Go, приступим к их выполнению. Это будет предполагать сложную реализацию исполнительной системы, которая отвечает за распределение заданий между серверами.

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


Борис Бабань,

создатель и технический директор ТУРБО



Получить дайджест
«Сила кода» на почту

Название компании
Отрасль
ФИО
Должность
E-mail
Made on
Tilda