Статьи

Секретные агенты помогают вашему коду обрабатывать параллелизм

Среди всех доступных вариантов безопасного и правильного управления общим состоянием в параллельных программах я выбрал концепцию « Агенты» для своего сегодняшнего поста. Я опишу принцип и покажу использование Агентов в Groovy , используя библиотеку GParallelizer .

Храбрый из вас, кто постоянно живет на грани, возможно, попробовал Clojure , функциональный язык программирования JVM, похожий на шутку. Среди других хитроумных конструкций параллелизма Clojure предоставляет Агенты, легкие, подобные объектам объекты, обертывающие общие изменяемые значения. Я нахожу концепцию довольно многообещающей.

Представьте, что агенты — это независимые активные объекты ( актеры ), полностью инкапсулирующие их внутреннее состояние, которые не предоставляют никаких методов, кроме операции send () , посредством которой вы можете передавать сообщения агенту. Однако и самое главное, агенты принимают только блоки кода (лямбды, замыкания, функции) в качестве сообщений. Вы отправляете им код, а не данные. Ты со мной?

Например, если агентами были люди, вы не могли бы сказать агенту: « Эй, приготовь мне еду, вот рецепт »,

agent.cookMeal(recipe)

или

agent.send(recipe)

но вместо этого вам придется сказать что-то вроде: « Эй, вот мой повар, пусть он готовит еду на вашей кухне ».

agent.send(myChef)

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

Схематически сценарий многопоточного взаимодействия с агентом может выглядеть примерно так:

agent = new Agent(0)  //created a new Agent wrapping an integer with initial value 0
agent.send {increment()} //asynchronous send operation, sending the increment() function

thread {
agent.send {increment()}
}

thread {
agent.send {double()}
agent.send {decrement()}
}

...

//after some delay to process the message the internal Agent's state has been updated

...

println 'The current snapshot of the value is: ' + agent.val

Кредитное плечо агентов в Groovy

Библиотека GParallelizer реализует концепцию агентов в классе SafeVariable , которая использует реализацию актера GParallelizer . Экземпляры SafeVariable переносят изменяемое состояние и принимают закрытия Groovy в качестве сообщений . Поставляемые затворы по очереди вызываются потоком SafeVariable . Они получают состояние внутреннего агента, переданное в качестве параметра, и могут свободно изменять его или заменять совершенно новым экземпляром.

def registrations = new SafeVariable([])  //we're wrapping a list
registrations.send {it.add 'Joe'} //safely adding without risk of races
registrations.send {it.add 'Dave'} //from other threads making their own registrations

Проверка текущего состояния агента становится вопросом получения свойства val.

println 'Currently registered volunteers: ' + registrations.val

Сглаживание API

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

import org.gparallelizer.actors.pooledActors.SafeVariable

class ShoppingCart {
private def cartState = new SafeVariable([:])

//----------------- public methods below here ----------------------------------

public void addItem(String product, int quantity) {
cartState << {it[product] = quantity} //the << operator sends
//a message to the SafeVariable
}

public void removeItem(String product) {
cartState << {it.remove(product)}
}

public Object listContent() {
return cartState.val
}

public void clearItems() {
cartState << performClear
}

public void increaseQuantity(String product, int quantityChange) {
cartState << this.&changeQuantity.curry(product, quantityChange)
}

//----------------- private methods below here ---------------------------------

private void changeQuantity(String product, int quantityChange, Map items) {
items[product] = (items[product] ?: 0) + quantityChange
}

private Closure performClear = { it.clear() }
}


//----------------- script code below here -------------------------------------


final ShoppingCart cart = new ShoppingCart()
cart.addItem 'Pilsner', 10
cart.addItem 'Budweisser', 5
cart.addItem 'Staropramen', 20

cart.removeItem 'Budweisser'
cart.addItem 'Budweisser', 15

println "Contents ${cart.listContent()}"

cart.increaseQuantity 'Budweisser', 3
println "Contents ${cart.listContent()}"

cart.clearItems()
println "Contents ${cart.listContent()}"

Вы могли заметить две стратегии реализации в коде.

1. Публичные методы могут внутренне просто отправлять требуемый код агенту, вместо того чтобы выполнять те же функции напрямую

И так последовательный код, как

public void addItem(String product, int quantity) {
cartState[product]=quantity
}

становится

public void addItem(String product, int quantity) {
cartState << {it[product] = quantity}
}

2. Открытые методы могут отправлять ссылки на внутренние частные методы или замыкания, которые содержат желаемую функциональность для выполнения

public void clearItems() {
cartState << performClear
}

private Closure performClear = { it.clear() }

Карринг может потребоваться , если замыкание принимает другие аргументы, кроме текущего внутреннего экземпляра состояния. См. Метод увеличения количества .

Вывод

Я чувствую, что агенты могут предложить довольно безопасные и вместе с тем полезные модели программирования для общего изменяемого состояния. Реализация в
GParallelizer доступна для вас, чтобы попробовать и использовать. Проверьте
GParallelizer SafeVariable вики .

Я буду очень рад, если вы
вернетесь с отзывами , предложениями и идеями. Наслаждайтесь Groovy параллелизмом!