Зачем нам нужны волокна



для волокон мы получили классический пример: генерация чисел Фибоначчи



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 }


вернет точно такой же результат.



Итак, каковы преимущества волокон. Что я могу написать с волокнами, которые я не могу сделать с лямбдами и другими крутыми функциями Ruby?

642   2  

2 ответов:

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

вероятно, #1 Использование волокон в Ruby заключается в реализации Enumerators, которые являются основным классом 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 S-это перечислимые объекты, и их each методы дают элементы, которые были бы получены с помощью исходного метода итератора, если бы он был вызван с блоком. В Примере, который я только что дал, перечислитель возвращается 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 должен был бы проверить это состояние и обновить его перед возвратом значения. С волокнами, мы можем автоматически преобразуйте любой внутренний итератор во внешний.

это не имеет отношения к волокнам persay, но позвольте мне упомянуть еще одну вещь, которую вы можете сделать с Перечислителями: они позволяют применять перечисляемые более высокого порядка методы для других итераторов, отличных от each. Подумайте об этом: обычно все перечисляемые методы, включая map,select,include?,inject и так далее все работа над элементами, полученными 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]

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

возвращаясь к волокнам, вы использовали элемент take метод с Перечислимым?

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

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

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

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

вместо явного сохранения этого состояния и проверки его каждый раз, когда клиент подключается (чтобы увидеть, что следующий "шаг" они должны сделать), вы можете поддерживать волокно для каждого клиента. После идентификации клиента, вы бы получить их волокна и повторно запустить его. Затем в конце каждого соединения вы приостанавливаете волокно и сохраняете его снова. Таким образом, вы могли бы написать линейный код для реализации всей логики для полного взаимодействия, включая все шаги (так же, как вы, естественно, если бы ваша программа была запущена локально).

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

В отличие от замыканий, которые имеют определенную точку входа и выхода, волокна могут сохранять свое состояние и возвращать (выход) много раз:

f = Fiber.new do
  puts 'some code'
  param = Fiber.yield 'return' # sent parameter, received parameter
  puts "received param: #{param}"
  Fiber.yield #nothing sent, nothing received 
  puts 'etc'
end

puts f.resume
f.resume 'param'
f.resume

печатает это:

some code
return
received param: param
etc

реализация этой логики с другими функциями ruby будет менее читаемой.

С этой особенностью, хорошее использование волокон сделать ручное кооперативное планирование (как замена потоков). У Ильи Григорика есть хороший пример того, как включить асинхронную библиотеку (eventmachine в этом случае) в что выглядит как синхронный API без потери преимуществ IO-планирования асинхронного выполнения. Вот это ссылке.

Comments

    Ничего не найдено.