Волокна - это то, что вы, вероятно, никогда не будете использовать непосредственно в коде уровня приложения.Они представляют собой примитив управления потоком, который можно использовать для создания других абстракций, которые вы затем используете в коде более высокого уровня.
Вероятно, использование волокон # 1 в Ruby состоит в реализации Enumerator
s, чтоосновной класс Ruby в Ruby 1.9. невероятно полезно.
В Ruby 1.9, если вы вызываете почти любой метод итератора для базовых классов, без прохождения блока, он вернет Enumerator
.
irb(main):001:0> [1,2,3].reverse_each
=> #<Enumerator: [1, 2, 3]:reverse_each>
irb(main):002:0> "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):003:0> 1.upto(10)
=> #<Enumerator: 1:upto(10)>
Эти Enumerator
являются перечислимыми объектами, а их методы each
дают элементы, которые были бы получены исходным методом итератора, если бы он был вызван с блоком.В приведенном мною примере Enumerator, возвращаемый reverse_each
, имеет метод each
, который дает 3,2,1.Перечислитель, возвращаемый chars
, возвращает «c», «b», «a» (и т. Д.).НО, в отличие от исходного метода итератора, перечислитель может также возвращать элементы один за другим, если вы постоянно вызываете next
:
irb(main):001:0> e = "abc".chars
=> #<Enumerator: "abc":chars>
irb(main):002:0> e.next
=> "a"
irb(main):003:0> e.next
=> "b"
irb(main):004:0> e.next
=> "c"
Возможно, вы слышали о «внутренних итераторах» и «внешних итераторах»«(хорошее описание обоих приведено в книге« Банды четырех »).Приведенный выше пример показывает, что перечислители можно использовать для превращения внутреннего итератора во внешний.
Это один из способов создания собственных перечислителей:
class SomeClass
def an_iterator
# note the 'return enum_for...' pattern; it's very useful
# enum_for is an Object method
# so even for iterators which don't return an Enumerator when called
# with no block, you can easily get one by calling 'enum_for'
return enum_for(:an_iterator) if not block_given?
yield 1
yield 2
yield 3
end
end
Давайте попробуем это:
e = SomeClass.new.an_iterator
e.next # => 1
e.next # => 2
e.next # => 3
Подожди минутку ... что-нибудь там кажется странным?Вы написали операторы yield
в an_iterator
как прямой код, но перечислитель может запускать их по одному .Между вызовами на next
выполнение an_iterator
«заморожено».Каждый раз, когда вы вызываете next
, он продолжает работать до следующего оператора yield
, а затем снова «зависает».
Можете ли вы догадаться, как это реализовано?Перечислитель упаковывает вызов к an_iterator
в волокне и передает блок, который приостанавливает волокно .Поэтому каждый раз, когда an_iterator
уступает блоку, волокно, на котором он работает, приостанавливается, и выполнение продолжается в главном потоке.В следующий раз, когда вы вызываете next
, он передает управление волокну, блок возвращает , и an_iterator
продолжается там, где остановился.
Было бы полезно подумать о том, чтобыть обязаны делать это без волокон.КАЖДЫЙ класс, который хотел бы предоставить как внутренние, так и внешние итераторы, должен был бы содержать явный код для отслеживания состояния между вызовами next
.Каждый вызов next должен проверять это состояние и обновлять его перед возвратом значения.С волокнами мы можем автоматически преобразовать любой внутренний итератор во внешний.
Это не имеет отношения к волокнам, но позвольте мне упомянуть еще одну вещь, которую вы можете сделать с помощью перечислителей: они позволяют применять перечислимые методы высшего порядка к другим итераторам, кроме each
.Подумайте об этом: обычно все методы Enumerable, включая map
, select
, include?
, inject
и т. Д., all работают с элементами, полученными с помощью each
.Но что, если у объекта есть другие итераторы, отличные от each
?
irb(main):001:0> "Hello".chars.select { |c| c =~ /[A-Z]/ }
=> ["H"]
irb(main):002:0> "Hello".bytes.sort
=> [72, 101, 108, 108, 111]
Вызов итератора без блока возвращает Enumerator, а затем вы можете вызвать другие методы Enumerable для этого.
Возвращаясь к волокнам, вы использовали метод take
из Enumerable?
class InfiniteSeries
include Enumerable
def each
i = 0
loop { yield(i += 1) }
end
end
Если что-то вызывает этот метод each
, похоже, он никогда не вернется, верно?Проверьте это:
InfiniteSeries.new.take(10) # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Я не знаю, использует ли это волокна под капотом, но это могло бы.Волокна могут использоваться для реализации бесконечных списков и ленивых вычислений ряда.Для примера некоторых ленивых методов, определенных с помощью перечислителей, я определил некоторые здесь: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Вы также можете построить средство сопрограммы общего назначения, используя волокна.Я никогда не использовал сопрограммы ни в одной из своих программ, но это хорошая идея, чтобы знать.
Надеюсь, это даст вам некоторое представление о возможностях. Как я уже говорил в начале, волокна являются низкоуровневым примитивом управления потоком. Они позволяют поддерживать несколько «позиций» потока управления в вашей программе (например, различные «закладки» на страницах книги) и переключаться между ними по желанию. Поскольку произвольный код может выполняться в волокне, вы можете вызвать сторонний код на волокне, а затем «заморозить» его и продолжить делать что-то еще, когда он перезвонит в код, которым вы управляете.
Представьте себе что-то вроде этого: вы пишете серверную программу, которая будет обслуживать множество клиентов. Полное взаимодействие с клиентом включает в себя выполнение ряда шагов, но каждое соединение является временным, и вы должны помнить состояние для каждого клиента между соединениями. (Звучит как веб-программирование?)
Вместо того, чтобы явно хранить это состояние и проверять его каждый раз, когда клиент подключается (чтобы увидеть, каким будет следующий «шаг», который он должен сделать), вы могли бы поддерживать оптоволокно для каждого клиента. После идентификации клиента вы должны получить его волокно и перезапустить его. Затем в конце каждого соединения вы приостановите оптоволокно и сохраните его снова. Таким образом, вы могли бы написать прямой код для реализации всей логики для полного взаимодействия, включая все шаги (как вы, естественно, сделали бы, если бы ваша программа была запущена локально).
Я уверен, что есть много причин, почему такая вещь может быть непрактичной (по крайней мере, на данный момент), но опять же, я просто пытаюсь показать вам некоторые возможности. Кто знает; как только вы получите концепцию, вы можете придумать какое-нибудь совершенно новое приложение, о котором еще никто не думал!