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