Для волокон у нас есть классический пример: генерация чисел Фибоначчи.
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y,x+y
end
end
Зачем нам нужны волокна? Я могу переписать это с помощью того же Proc (фактически, закрытие)
def clsr
x, y = 0, 1
Proc.new do
x, y = y, x + y
x
end
end
Так
10.times { puts fib.resume }
и
prc = clsr
10.times { puts prc.call }
вернет точно такой же результат.
Итак, в чем преимущества волокон. Какие вещи я могу писать с помощью Fibers, чего не могу делать с лямбдами и другими классными функциями Ruby?
Ответы:
Волокна - это то, что вы, вероятно, никогда не будете использовать непосредственно в коде уровня приложения. Это примитив управления потоком, который вы можете использовать для создания других абстракций, которые затем вы используете в коде более высокого уровня.
Вероятно, использование волокон №1 в Ruby - это реализация
Enumerator
s, которые являются основным классом Ruby в Ruby 1.9. Это невероятно полезно.В Ruby 1.9, если вы вызываете почти любой метод итератора в основных классах без передачи блока, он вернет файл
Enumerator
.Это
Enumerator
Enumerable объекты, и ихeach
методы выдают элементы, которые были бы выданы исходным методом итератора, если бы он был вызван с блоком. В примере, который я только что привел, возвращаемый Enumeratorreverse_each
имеетeach
метод, который дает 3,2,1. Перечислитель, возвращаемый функцией,chars
дает "c", "b", "a" (и так далее). НО, в отличие от исходного метода итератора, Enumerator также может возвращать элементы один за другим, если вы вызываетеnext
его повторно:Возможно, вы слышали о «внутренних итераторах» и «внешних итераторах» (хорошее описание обоих приведено в книге «Банда четырех» шаблонов проектирования). В приведенном выше примере показано, что перечислители можно использовать для превращения внутреннего итератора во внешний.
Это один из способов создать свои собственные счетчики:
Давай попробуем:
Погодите ... не кажется ли вам что-нибудь странным? Вы написали
yield
операторыan_iterator
как прямой код, но Enumerator может запускать их по одному . В промежутках между вызовамиnext
выполнениеan_iterator
"замораживается". Каждый раз, когда вы звонитеnext
, он продолжает работать до следующегоyield
оператора, а затем снова «зависает».Угадаете, как это реализовано? Enumerator завершает вызов
an_iterator
в волокне и передает блок, который приостанавливает волокно . Таким образом, каждый раз, когдаan_iterator
уступает место блоку, волокно, на котором он работает, приостанавливается, и выполнение продолжается в основном потоке. В следующий раз, когда вы вызоветеnext
, он передает управление волокну, блок возвращается иan_iterator
продолжает работу с того места, где он остановился.Было бы поучительно подумать, что для этого потребуется без волокон. КАЖДЫЙ класс, который хотел предоставить как внутренние, так и внешние итераторы, должен был бы содержать явный код для отслеживания состояния между вызовами
next
. Каждый вызов next должен будет проверять это состояние и обновлять его перед возвратом значения. С помощью фибров мы можем автоматически преобразовать любой внутренний итератор во внешний.Это не имеет отношения к persay-волокнам, но позвольте мне упомянуть еще об одной вещи, которую вы можете делать с помощью Enumerators: они позволяют вам применять методы Enumerable более высокого порядка к другим итераторам, кроме
each
. Подумайте об этом: обычно все Перечислимые методы, в том числеmap
,select
,include?
,inject
, и так далее, все работы на элементах получены путемeach
. Но что, если у объекта есть другие итераторы, кромеeach
?Вызов итератора без блока возвращает Enumerator, а затем вы можете вызывать для него другие методы Enumerable.
Возвращаясь к волокнам, вы использовали
take
метод из Enumerable?Если что-то вызывает этот
each
метод, похоже, он никогда не должен возвращаться, верно? Проверь это:Я не знаю, используются ли волокна под капотом, но может. Волокна могут использоваться для реализации бесконечных списков и ленивого вычисления ряда. В качестве примера некоторых ленивых методов, определенных с помощью Enumerators, я определил некоторые здесь: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Вы также можете построить средство сопрограмм общего назначения, используя волокна. Я еще никогда не использовал сопрограммы ни в одной из своих программ, но это хорошая идея, которую нужно знать.
Надеюсь, это дает вам некоторое представление о возможностях. Как я сказал в начале, волокна представляют собой примитив управления потоком низкого уровня. Они позволяют поддерживать несколько «позиций» потока управления в вашей программе (например, разные «закладки» на страницах книги) и переключаться между ними по желанию. Поскольку произвольный код может выполняться в волокне, вы можете вызвать сторонний код в волокне, а затем «заморозить» его и продолжить делать что-то еще, когда он обратится в код, который вы контролируете.
Представьте себе что-то вроде этого: вы пишете серверную программу, которая будет обслуживать множество клиентов. Полное взаимодействие с клиентом включает в себя выполнение ряда шагов, но каждое соединение является временным, и вы должны помнить состояние каждого клиента между соединениями. (Похоже на веб-программирование?)
Вместо того, чтобы явно сохранять это состояние и проверять его каждый раз, когда клиент подключается (чтобы увидеть, какой следующий «шаг» им нужно сделать), вы можете поддерживать волокно для каждого клиента. После идентификации клиента вы получите его волокно и перезапустите его. Затем в конце каждого подключения вы должны подвешивать волокно и снова хранить его. Таким образом, вы можете написать прямолинейный код для реализации всей логики полного взаимодействия, включая все шаги (точно так же, как если бы ваша программа была запущена локально).
Я уверен, что есть много причин, по которым такая вещь может оказаться непрактичной (по крайней мере, на данный момент), но, опять же, я просто пытаюсь показать вам некоторые возможности. Кто знает; как только вы усвоите концепцию, вы можете придумать совершенно новое приложение, о котором еще никто не придумал!
источник
chars
другие перечислители с простыми замыканиями?Enumerable
в Ruby 2.0 будут включены некоторые «ленивые» методы.take
не требует волокна. Вместо этогоtake
просто ломается во время n-го выхода. При использовании внутри блокаbreak
возвращает управление фрейму, определяющему блок.a = [] ; InfiniteSeries.new.each { |x| a << x ; break if a.length == 10 } ; a
В отличие от замыканий, которые имеют определенную точку входа и выхода, волокна могут сохранять свое состояние и возвращать (выходить) много раз:
печатает это:
Реализация этой логики с другими функциями Ruby будет менее читабельной.
С этой функцией хорошее использование волокон - это ручное совместное планирование (как замена потоков). У Ильи Григорика есть хороший пример того, как превратить асинхронную библиотеку (
eventmachine
в данном случае) в то, что выглядит как синхронный API, без потери преимуществ IO-планирования асинхронного выполнения. Вот ссылка .источник
physical meaning
на более простом примере