Книга: Программирование на языке Ruby
20.3. Rinda: пространство кортежей в Ruby
Разделы на этой странице:
20.3. Rinda: пространство кортежей в Ruby
Термин «пространство кортежей» появился в 1985 году, а сама идея еще старше. Кортежем называется массив или вектор, состоящий из элементов данных (как строка в таблице базы данных). Пространство кортежей — это большое объектное пространство, наполненное кортежами, нечто вроде «информационного супа».
Пока реализация пространства кортежей кажется ничем не примечательной. Но все становится гораздо интереснее, стоит лишь осознать, что к нему могут обращаться многие клиенты и доступ должен синхронизироваться. Короче говоря, это распределенная сущность; любой клиент может читать из пространства кортежей или писать в него, то есть его можно рассматривать как большое распределенное хранилище или даже способ коммуникации.
Первой реализацией пространства кортежей был проект Linda — исследование в области параллельного программирования, выполненное в Йельском университете в 1980-х годах. Реализация на языке Ruby (конечно, на основе библиотеки drb
), естественно, называется Rinda.
Кортеж в Rinda может быть массивом или хэшем. На хэш налагается дополнительное ограничение: все ключи должны быть строками. Вот несколько примеров простых кортежей:
t1 = [:add, 5, 9]
t2 = [:name, :add_service, Adder.new, nil]
t3 = { 'type' => 'add', 'value_1' => 5, 'value_2' => 9 }
Элемент кортежа может быть произвольным объектом; это работает, потому что drb
умеет выполнять маршалинг и демаршалинг объектов Ruby. (Конечно, необходимо либо включить модуль DRbUndumped
, либо сделать определения объектов доступными серверу.)
Пространство объектов создается методом new
:
require 'rinda/tuplespace'
ts = Rinda::TupleSpace.new
# ...
Поэтому сервер выглядит так:
require 'rinda/tuplespace'
ts = Rinda::TupleSpace.new
DRb.start_service("druby://somehost:9000", ts)
gets # Нажать Enter для завершения сервера.
А клиент — так:
require 'rinda/tuplespace'
DRb.start_service
ts = DRbObject.new(nil, "druby://somehost:9000")
# ...
К пространству кортежей в Rinda применимы пять операций: read
, read_all
, write
, take
и notify
.
Операция чтения read
позволяет получить один кортеж. Но способ идентификации кортежа не вполне очевиден: необходимо задать кортеж, соответствующий искомому; при этом nil
соответствует любому значению.
t1 = ts.read [:Sum,nil] # Может извлечь, например, [:Sum, 14].
Обычно операция read
блокирует выполнение программы (для синхронизации). Чтобы быстро проверить существование кортежа, можно выполнить неблокирующее чтение, задав нулевой тайм-аут:
t2 = ts.read [:Result,nil],0 # Возбуждает исключение, если кортеж
# не существует.
Если мы точно знаем или предполагаем, что образцу будет соответствовать не один, а несколько кортежей, можно воспользоваться методом read_all
, который возвращает массив:
tuples = ts.read_all [:Foo, nil, nil]
tuples.each do |t|
# ...
end
Метод read_all
не принимает второго параметра. Он всегда блокирует программу, если не найдено ни одного подходящего кортежа.
Операция take
— это чтение, за которым следует удаление. Иными словами, метод take
удаляет кортеж из пространства кортежей и возвращает его вызывающей программе:
t = ts.take [:Sum, nil] # Кортежа больше нет в пространстве кортежей.
Может возникнуть вопрос, почему не существует явного способа удаления. Надо полагать, что этой цели служит метод take.
Метод write
помещает кортеж в пространство кортежей. Второй параметр показывает, сколько секунд кортеж может существовать, прежде чем система сочтет, что срок его хранения истек. (По умолчанию его значение равно nil
, то есть срок хранения не ограничен.)
ts.write [:Add, 5, 9] # Хранить "вечно".
ts.write [:Foo, "Bar"], 10 # Хранить 10 секунд.
Здесь уместно будет сказать несколько слов о синхронизации. Предположим, что два клиента пытаются одновременно забрать (take
) один и тот же кортеж. Одному это удастся, а другой будет заблокирован. Если первый клиент затем изменит кортеж и запишет (write
) его обратно в хранилище, то второй получит модифицированную версию. Можно считать, что операция «обновления» — это последовательность take
и write
, которая не приводит к потере данных. Конечно, как и при любом варианте многопоточного программирования, нужно позаботиться о том, чтобы не возникали тупиковые ситуации.
Метод notify
позволяет следить за пространством кортежей и получать уведомления, когда над интересующим вас кортежем была выполнена какая-то операция. Этот метод возвращает объект NotifyTemplateEntry
и может наблюдать на операциями четырех видов:
• write
;
• take
;
• удаление (когда истекает срок хранения кортежа);
• закрытие (когда истекает срок хранения объекта NotifyTemplateEntry
).
Поскольку операция чтения ничего не изменяет, то система не поддерживает уведомлений о чтениях. В листинге 20.4 приведен пример использования notify.
Листинг 20.4. Уведомление в системе Rinda
require 'rinda/tuplespace'
ts = Rinda::TupleSpace.new
alberts = ts.notify "write", ["Albert", nil]
martins = ts.notify "take", ["Martin", nil]
thr1 = Thread.new do
alberts.each {|op,t| puts "#{op}: #{t.join(' ')}" }
end
thr2 = Thread.new do
martins.each {|op,t| puts "#{op}: #{t.join(' ')}" }
end
sleep 1
ts.write ["Martin", "Luther"]
ts.write ["Albert", "Einstein"]
ts.write ["Martin", "Fowler"]
ts.write ["Alberf, "Schweitzer"]
ts.write ["Martin", "Scorsese"]
ts.take ["Martin", "Luther"]
# Выводится:
# write: Albert Einstein
# write: Albert Schweitzer
# take: Martin Luther
Мы видели, что read
и другие операции пользуются шаблонами для сопоставления с образцами (и этим напоминают регулярные выражения). Мы уже знаем, что nil
выступает в роли метасимвола, но можно указать и класс; ему будет соответствовать любой экземпляр этого класса.
tem1 = ["X", Integer] # Соответствует ["X",5], но не ["X","Files"].
tem2 = ["X", NilClass] # Соответствует литералу nil в кортеже.
Кроме того, разрешается определять собственный оператор ветвящегося равенства (===
), если вы хотите проводить сопоставление особым способом. В противном случае для сравнения будет использован стандартный оператор ===
.
Время жизни кортежа можно задать в момент записи. В сочетании с величинами тайм-аутов для различных операций над кортежами это позволяет ограничить время выполнения простых и более сложных манипуляций.
Тот факт, что у кортежа может быть конечный срок хранения, заодно означает, что по истечении этого срока кортеж можно обновить с помощью специально написанного объекта. В библиотеке имеется готовый класс SimpleRenewer
, который каждые 180 секунд обращается к drb-серверу, создавшему кортеж. Если сервер не отвечает, то кортеж удаляется. Но не пытайтесь программировать обновление, пока не освоитесь с парадигмой пространства кортежей.
В листинге 20.5 приведен еще один пример работы с пространством кортежей. Он решает ту же задачу о производителе и потребителе, которая была рассмотрена в главе 13.
Листинг 20.5. Задача о производителе и потребителе
require 'rinda/tuplespace'
ts = Rinda::TupleSpace.new
producer = Thread.new do
item = 0
loop do
sleep rand(0)
puts "Производитель произвел ##{item}"
ts.write ["Item",item]
item += 1
end
end
consumer = Thread.new do
loop do
sleep rand(0)
tuple = ts.take ["Item", nil]
word, item = tuple
puts "Потребитель потребил ##{item}"
end
end
sleep 60 # Работать одну минуту, потом завершиться и завершить потоки.
- Глава 4. Интернационализация в Ruby
- Глава 20. Распределенный Ruby
- 20.4. Обнаружение сервисов в распределенном Ruby
- Глава 1. Обзор Ruby
- 1.2. Базовый синтаксис и семантика Ruby
- 1.3. ООП в Ruby
- 1.4. Динамические аспекты Ruby
- 3. Схемы отношений. Именованные значения кортежей
- 4. Кортежи. Типы кортежей
- 6.3 Пространство имен устройств
- 12.5.1. Ruby и X
- 12.5.2. Ruby и wxWidgets