# ...
Поэтому сервер выглядит так:
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.
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 в кортеже.
Кроме того, разрешается определять собственный оператор ветвящегося равенства (===
), если вы хотите проводить сопоставление особым способом. В противном случае для сравнения будет использован стандартный оператор ===
.
Время жизни кортежа можно задать в момент записи. В сочетании с величинами тайм-аутов для различных операций над кортежами это позволяет ограничить время выполнения простых и более сложных