Статьи

Введение в целлулоид, часть II

Это вторая статья из трех частей. Если вы пропустили первый, вы можете найти его здесь

В Celluloid есть множество более удивительных инструментов, которые делают параллельное программирование невероятно простым в Ruby.

Давайте посмотрим на них.

фьючерсы

Бывают случаи, когда мы не хотим просто отбрасывать возвращаемое значение метода, который мы вызвали для актера; вместо этого мы могли бы хотеть использовать это где-нибудь еще. Для этого целлулоид обеспечивает фьючерсы . Лучший способ узнать о них — увидеть их в действии.

Мы напишем небольшой скрипт, который вычисляет контрольную сумму SHA1 для массива файлов, а затем выводит их на консоль.

Без лишних слов, вот оно:

require ‘celluloid’
require ‘digest/sha1’
class SHAPutter
include Celluloid
def initialize(filename)
@filename = filename
end
def output(checksum_future)
puts «#{@filename}#{checksum_future.value}«
end
def checksum
@file_contents = File.read(@filename)
Digest::SHA1.hexdigest @file_contents
end
end
files = [«/var/log/kernel.log», «/var/log/system.log», «/var/log/ppp.log», «/var/log/secure.log»]
files.each do |file|
sha = SHAPutter.new file
checksum_future = sha.future :checksum
sha.output checksum_future
end

view raw
gistfile1.rb
hosted with ❤ by GitHub

Прежде всего, рассмотрим метод контрольной суммы. Это довольно просто, мы используем Digest :: SHA1, чтобы вычислить контрольную сумму содержимого файла, который предоставляется субъекту.

Посмотрите на цикл files.each. Вот где это становится интересным.

Сначала мы создаем актера и назначаем ему файл. Затем, вместо того, чтобы просто вызывать метод контрольной суммы, мы вызываем его, используя будущее. При этом объект Celluloid :: Future сразу возвращается, а не блокируется.

Затем мы берем этот будущий объект и передаем его в метод вывода внутри актера.
Внутри метода вывода значение контрольной суммы необходимо! Таким образом, это достигается из метода значения будущего объекта, который блокируется, пока значение не станет доступным. Это решает проблему!

Возможно, вы подумали: «эй, это примерно то же самое, что и в последнем примере!» Однако в последнем примере для асинхронного выполнения операций с файлами мы свалили все в один метод. С помощью фьючерсов мы можем четко отделить наш код.

Кроме того, существуют случаи, когда возможно использование только фьючерсов. Например, если кто-то пишет библиотеку, результатом функции контрольной суммы должно быть будущее, поскольку пользователь библиотеки должен иметь возможность добавлять свой собственный код.

Создание любого блока одновременно

Есть очень крутое применение для фьючерсов, а именно, они позволяют нам невероятно легко перенести блок кода в другой поток.

Проверьте это:

require ‘celluloid’
def some_method(future)
#do something crazy
val = future.value
#do something with val
end
future = Celluloid::Future.new do
#incredibly complex computation
end
some_method(future)

view raw
gistfile1.rb
hosted with ❤ by GitHub

Мы используем Celluloid :: Future, чтобы вставить блок в свой собственный поток. Celluloid управляет всем в этом потоке, возвращаемое значение которого мы можем использовать позже (конечно, используя возвращаемое значение будущего). Таким образом, эта небольшая часть Celluloid может быть подключена буквально к любому приложению и, будучи освоенной, может быть невероятно полезной.

Использовать его мудро!

Ловля ошибок — Супервайзеры

Чтобы увидеть, как работает обработка ошибок в Celluloid, мы собираемся создать простой инструмент, который получает HTML-код различных веб-сайтов.

Вот то, что мы узнали до сих пор:

require ‘celluloid’
require ‘net/http’
class MarkupPutter
include Celluloid
def initialize(url)
@url = url
end
def output(markup_future)
puts «#{@url}«
puts «#{markup_future.value}«
puts «—————————-«
end
def get_markup
Net::HTTP.get(URI.parse(@url))
end
end
websites = [«http://google.com/», «http://yahoo.com/», «http://rubysource.com/», «http://tumblr.com/»]
websites.each do |website|
mp = MarkupPutter.new website
markup_future = mp.future :get_markup
mp.output markup_future
end

view raw
gistfile1.rb
hosted with ❤ by GitHub

Если все идет хорошо, разметка puts

Но что, если что-то пойдет не так? Мы на самом деле мало что делаем с этим.

Для этой цели Celluloid предоставляет механизм, известный как супервизор. Вот оно в действии:

require ‘celluloid’
require ‘net/http’
class MarkupPutter
include Celluloid
def initialize(url)
@url = url
end
def output(markup_future)
puts «#{@url}«
puts «#{markup_future.value}«
puts «—————————-«
end
def get_markup
Net::HTTP.get(URI.parse(@url))
end
end
websites = [«http://google.com/», «http://yahoo.com/», «http://rubysource.com/», «http://tumblr.com/»]
websites.each do |website|
supervisor = MarkupPutter.supervise_as :mp, website
mp = Celluloid::Actor[:mp]
markup_future = mp.future :get_markup
mp.output markup_future
end

view raw
gistfile1.rb
hosted with ❤ by GitHub

Здесь есть несколько новых концепций, поэтому обратите пристальное внимание.

Прежде всего, класс MarkupPutter Другими словами, реализация бизнес-логики осталась без изменений!

Теперь мы вызываем метод superviseMarkupPutter Это делает три вещи, во-первых, он создает (и приводит в движение) актер, который является экземпляром MarkupPutter Во-вторых, он возвращает объект супервизора, который может делать некоторые интересные вещи. Наконец, он принимает свой первый аргумент (это «mp») и помещает запись с таким именем в реестр.

Реестр Celluloid немного похож на телефонную книгу — актеры, которые находятся там, могут быть доступны по имени. Итак, на следующей строке мы используем реестр Celluloid для поиска :mp

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

С добавлением двух строк кода Celluloid автоматически позаботится о перезапуске и отслеживании актеров, когда они терпят крах!

В случае, если один из участников обнаруживает какое-то исключение (например, веб-сайт не отвечает на запрос и время ожидания запроса истекает), субъект немедленно перезапускается ядром Celluloid. Если вы написали этот вид многопоточного кода старомодным способом, вы знаете, что это очень сложный и сложный процесс, но он полностью обрабатывается Celluloid для нас!

Общение между актерами

Практически во всех приложениях актеры не будут работать в изолированных средах — они будут общаться с другими субъектами.

Просто чтобы объяснить, как общение между актерами работает в Целлулоиде, мы напишем трех актеров, чтобы распечатать «Привет, мир!» При правильном запуске. Проверьте это:

require ‘celluloid’
class HelloSpaceActor
include Celluloid
def say_msg
print «Hello, «
Celluloid::Actor[:world].say_msg
end
end
class WorldActor
include Celluloid
def say_msg
print «world!»
Celluloid::Actor[:newline].say_msg
end
end
class NewlineActor
include Celluloid
def say_msg
print «\n«
end
end
Celluloid::Actor[:world] = WorldActor.new
Celluloid::Actor[:newline] = NewlineActor.new
HelloSpaceActor.new.say_msg

view raw
gistfile1.rb
hosted with ❤ by GitHub

Мы начнем с определения трех актеров, каждый из которых говорит часть сообщения «Здравствуй, мир! N». HelloSpaceActorWorldActorsay_msgWorldActorNewlineActor

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

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

Блокировка звонков внутри актеров

Если у вас есть опыт работы с EventMachine, вы знаете, что вы не можете смешивать EventMachine с любой другой библиотекой для ввода-вывода — библиотека должна быть совместимой с EventMachine.

Таким образом, вы не можете использовать всю мощь сообщества Ruby. Вместо этого вы застряли в гораздо меньшем сообществе EventMachine.
С целлулоидом это не так!

Поскольку все актеры находятся в своих собственных потоках, вполне нормально, чтобы вызовы методов внутри акторов блокировались, поскольку он блокирует только одного актера!

Но будьте осторожны. Не делайте бесконечную блокировку вызовов у актеров (например, прослушивание сокета) — это приводит к тому, что все сообщения, идущие к этому актеру, становятся приостановленными, что плохо!

объединение

Если вы немного прочитали о том, как работают веб-серверы, вы знаете, насколько важны пулы потоков. Бассейны в целлулоиде потрясающие; они полностью прозрачны. Я думаю, что они, вероятно, моя любимая особенность Celluloid (с таким количеством классных вещей, трудно выбрать!).

Мы напишем простой пример, чтобы продемонстрировать, насколько они удивительны:

require ‘celluloid’
require ‘mathn’
class PrimeWorker
include Celluloid
def prime(number)
if number.prime?
puts number
end
end
end
pool = PrimeWorker.pool
(2..1000).to_a.map do |i|
pool.prime! i
end
sleep 100

view raw
gistfile1.rb
hosted with ❤ by GitHub

Сначала мы определим класс PrimeWorker «Рабочий» в имени означает, что он должен использоваться с пулом — потоки, которые являются частью пулов потоков, обычно называются рабочими.

Функция primePrimeWorker

Интересная часть, когда мы представляем пул, вызывая метод пула на PrimeWorker

Объект «пул» имеет все методы PrimeWorkerPrimeWorker Поэтому, если у вас есть четырехъядерный процессор, это создаст четырех актеров. Когда методы вызываются для «пула», Celluloid решает, какого актера из пула вызывать.

После этого у нас есть карта на большом диапазоне, в которой мы называем простой (помните, он вызывается асинхронно из-за взрыва) для пула. Это автоматически распределяет нагрузку на ваши процессоры!

Вау. Для достижения полного параллелизма потребовалось, может быть, четыре или пять строк кода. Это восхитительно.

В конце программы есть спящий вызов. Для этого есть веская причина. Так как мы вызываем prime Однако актеры фактически не печатают простые числа к моменту выхода из основного потока, поэтому вывод никогда не достигает терминала.

Но команда sleep поддерживает основной поток в течение достаточно долгого времени, чтобы все выходные данные выводились правильно. Также обратите внимание, что поскольку мы вызываем primeпорядка вывода простых чисел.

Завершение

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

До сих пор мы обсуждали, как использовать различные части целлулоида для отдельного использования с небольшими примерами.

В третьей части мы расскажем о том, как все это связать вместе, создадим несколько более сложных программ и расскажем о дополнительных функциях, таких как связывание.

Задавайте любые вопросы, которые у вас есть в разделе комментариев ниже 🙂