Статьи

Параллельное программирование в Groovy


Кажется, у Groovy есть проект для чего угодно.
Это одна из причин, почему язык так популярен. Библиотека
GPars является особенно полезным проектом в эту новую эру многоядерных процессоров и параллельного программирования. Ранее известный как GParallelizer, GPars предлагает платформу для одновременной и асинхронной обработки задач, одновременно защищая изменяемые значения. Недавно DZone получил обновленную информацию о последних новостях проекта от руководителя проекта Вацлава Печа, и мы представили несколько примеров концепций GPars.

GPars использует некоторые из лучших концепций из появляющихся языков и реализует их для Groovy. Поддержка актеров была вдохновлена ​​библиотекой Actors в Scala, а класс SafeVariable в GPars — агентами в Clojure. API на основе Groovy в GPars используются для объявления того, какие части кода должны выполняться одновременно. Объекты могут быть расширены с помощью асинхронных методов для параллельного выполнения операций на основе коллекций на основе модели fork / join. GPars также имеет модель параллелизма Dataflow, которая предлагает альтернативную модель, которая по своей природе безопасна и надежна благодаря алгоритмам, которые предотвращают необходимость иметь дело с живыми блокировками и условиями гонки. Класс SafeVariable — это еще одна технология в GPars, которая устраняет проблемы с параллелизмом, предоставляя неблокирующую mt-safe ссылку на изменяемое состояние при интеграции библиотек Java.Наконец, Parallelizer, Asynchronizer и Actors являются одними из самых интересных концепций в GPars.

Актеры

Актеры могут быть сгенерированы быстро, чтобы потреблять и отправлять сообщения между собой даже между распределенными машинами. Вы можете построить модель параллелизма на основе обмена сообщениями с субъектами, которые не ограничены количеством потоков. То, что раньше было доступно только разработчикам Scala, теперь GPars предлагает разработчикам на Java и Groovy.

Актеры выполняют три разные операции — отправляют сообщения, получают сообщения и создают новых актеров. Новые актеры создаются с помощью метода actor (), передаваемого в теле актора в качестве параметра замыкания. Внутри тела субъекта цикл () используется для итерации, реагирования () на получение сообщений и ответа () для отправки сообщения субъекту, который отправил текущее обработанное сообщение. Вот как создать актера, который распечатывает все полученные сообщения:

import static groovyx.gpars.actor.Actors.*
def console = actor {
loop {
react {
println it
}
}
}

Метод loop () гарантирует, что субъект не остановится после обработки первого сообщения.

Сообщения отправляются с использованием метода send () или оператора <<. Вот пример метода sendAndWait () в сообщении:

actor << 'Message'
actor.send 'Message'
def reply1 = actor.sendAndWait('Message')
def reply2 = actor.sendAndWait(10, TimeUnit.SECONDS, 'Message')
def reply3 = actor.sendAndWait(10.seconds, 'Message')

Семейство sendAndWait () блокирует вызывающего, пока не станет доступен ответ от актера. Ответ возвращается из sendAndWait () в качестве возвращаемого значения.  

При неблокирующем извлечении сообщения вызов метода реагирования () с параметром тайм-аута или без него из кода актера будет использовать следующее сообщение из входящей почты актера:

println 'Waiting for a gift'
react {gift ->
if (myWife.likes gift) reply 'Thank you!'
}

Вот более «реальный» пример актера, управляемого событиями, который получает два числовых сообщения, генерирует сумму и отправляет результат в консольный актер:

import static groovyx.gpars.actor.Actors.*
//not necessary, just showing that a single-threaded pool can still handle multiple actors
defaultPooledActorGroup.resize 1
final def console = actor {
loop {
react {
println 'Result: ' + it
}
}
}
final def calculator = actor {
react {a ->
react {b ->
console.send(a + b)
}
}
}
calculator.send 2
calculator.send 3
calculator.join()

Поскольку субъекты могут совместно использовать относительно небольшой пул потоков, они обходят ограничения потоков JVM и не требуют чрезмерных системных ресурсов, даже если приложение состоит из тысяч участников. Есть несколько более сложных примеров актеров на старой вики GParallelizer,
а также
хорошая статья о ключевых концепциях, стоящих за актерами в erlang и scala. Документацию по GPars Actors можно найти
здесь .

Asynchronizer

Основной особенностью GPars является класс Asynchronizer, который асинхронно выполняет задачи в фоновом режиме. Он включает DSL на основе службы Java Executor Service для коллекций и замыканий. Внутри блоков Asynchronizer.doParallel () асинхронные методы могут быть добавлены к замыканиям. async () создает вариант предоставленного замыкания, возвращая будущее для потенциального возвращаемого значения при вызове. callAsync () вызывает замыкание в отдельном потоке, предоставляющем заданные аргументы, а также возвращает будущее для потенциального значения. Вот один пример использования Asynchronizer:

Asynchronizer.doParallel() {
Closure longLastingCalculation = {calculate()}
Closure fastCalculation = longLastingCalculation.async() //create a new closure, which starts the original closure on a thread pool
Future result=fastCalculation() //returns almost immediately
//do stuff while calculation performs …
println result.get()
}

Parallelizer

Наконец, есть Parallelizer, который является параллельным процессором сбора данных. Общий шаблон для обработки коллекций принимает элементы последовательно, по одному за раз. Однако этот алгоритм не будет хорошо работать на многоядерном оборудовании. Функция min () на двухъядерном чипе может использовать только 50% вычислительной мощности — 25% для четырехъядерного. Вместо этого GPars использует древовидную структуру для параллельной обработки. Класс Parallelizer включает DSL на основе ParallelArray (из JSR-166y) для коллекций. Вот пример использования:

doParallel {
def selfPortraits = images.findAllParallel{it.contains me}.collectParallel {it.resize()}

//a map-reduce functional style
def smallestSelfPortrait = images.parallel.filter{it.contains me}.map{it.resize()}.min{it.sizeInMB}
}

Вацлав Печ рассказал DZone, что в GPars в настоящее время к проекту присоединились два новых человека — Джон Керридж и Кевин Чалмерс. Эти два разработчика приносят свою библиотеку JCSP Groovy вместе с ними в GPars. Печ сказал: «Помимо экспериментов с концепцией CSP, мы также улучшим удаленное взаимодействие акторов и отшлифуем несколько грубых краев API. Документация и особенно примеры также заслуживают большего внимания». Документация GPars уже достаточно надежна.

Печ говорит, что в их JIRA поставлено в очередь немало проблем, но перечисленные ранее проблемы остаются главными приоритетами. По словам Печа, в зависимости от количества времени, необходимого для работы с CSP, выпуск GPars 1.0 может состояться летом. GPars также разработан Алексом Ткачманом, ведущим разработчиком проекта Groovy ++.
В
интервью вместе с Андресом Алмиреем Ткачман сказал, что часть работы, которая выходит из Groovy ++, может быть ассимилирована в GPars, но никаких планов пока нет, поскольку разработчики Groovy ++ все еще экспериментируют.