Среди всех доступных вариантов безопасного и правильного управления общим состоянием в параллельных программах я выбрал концепцию « Агенты» для своего сегодняшнего поста. Я опишу принцип и покажу использование Агентов в 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 параллелизмом!