Это вторая статья из трех частей. Если вы пропустили первый, вы можете найти его здесь
В 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 |
Прежде всего, рассмотрим метод контрольной суммы. Это довольно просто, мы используем 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) |
Мы используем 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 |
Если все идет хорошо, разметка 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 |
Здесь есть несколько новых концепций, поэтому обратите пристальное внимание.
Прежде всего, класс MarkupPutter
Другими словами, реализация бизнес-логики осталась без изменений!
Теперь мы вызываем метод supervise
MarkupPutter
Это делает три вещи, во-первых, он создает (и приводит в движение) актер, который является экземпляром 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 |
Мы начнем с определения трех актеров, каждый из которых говорит часть сообщения «Здравствуй, мир! N». HelloSpaceActor
WorldActor
say_msg
WorldActor
NewlineActor
Короче говоря, общение актеров осуществляется с помощью Реестра актеров, где мы можем дать имена актеров.
Как мы знаем, еще один способ заставить актеров работать вместе — это фьючерсы — передавать фьючерсы между актерами, чтобы получить возвращаемые значения.
Блокировка звонков внутри актеров
Если у вас есть опыт работы с 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 |
Сначала мы определим класс PrimeWorker
«Рабочий» в имени означает, что он должен использоваться с пулом — потоки, которые являются частью пулов потоков, обычно называются рабочими.
Функция prime
PrimeWorker
Интересная часть, когда мы представляем пул, вызывая метод пула на PrimeWorker
Объект «пул» имеет все методы PrimeWorker
PrimeWorker
Поэтому, если у вас есть четырехъядерный процессор, это создаст четырех актеров. Когда методы вызываются для «пула», Celluloid решает, какого актера из пула вызывать.
После этого у нас есть карта на большом диапазоне, в которой мы называем простой (помните, он вызывается асинхронно из-за взрыва) для пула. Это автоматически распределяет нагрузку на ваши процессоры!
Вау. Для достижения полного параллелизма потребовалось, может быть, четыре или пять строк кода. Это восхитительно.
В конце программы есть спящий вызов. Для этого есть веская причина. Так как мы вызываем prime
Однако актеры фактически не печатают простые числа к моменту выхода из основного потока, поэтому вывод никогда не достигает терминала.
Но команда sleep поддерживает основной поток в течение достаточно долгого времени, чтобы все выходные данные выводились правильно. Также обратите внимание, что поскольку мы вызываем prime
порядка вывода простых чисел.
Завершение
Надеюсь, вам понравилась статья, и вы так же взволнованы целлулоидом, как и я.
До сих пор мы обсуждали, как использовать различные части целлулоида для отдельного использования с небольшими примерами.
В третьей части мы расскажем о том, как все это связать вместе, создадим несколько более сложных программ и расскажем о дополнительных функциях, таких как связывание.
Задавайте любые вопросы, которые у вас есть в разделе комментариев ниже 🙂