Волокна - це те, що ви, ймовірно, ніколи не використовуєте безпосередньо в коді на рівні додатків. Вони є примітивом управління потоком, який ви можете використовувати для створення інших абстракцій, які потім використовуєте у коді вищого рівня.
Ймовірно, використання №1 волокон у Ruby - це реалізація Enumerator
s, які є основним класом Ruby в Ruby 1.9. Це неймовірно корисні.
У Ruby 1.9, якщо ви викликаєте майже будь-який метод ітератора на основних класах, не передаючи блок, він поверне an 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
врожаях "с", "б", "а" (і так далі). АЛЕ, на відміну від оригінального методу ітератора, Перелік також може повертати елементи один за одним, якщо ви звертаєтесь 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
. Кожен дзвінок до наступного повинен був перевірити цей стан та оновити його, перш ніж повертати значення. За допомогою волокон ми можемо автоматично перетворити будь-який внутрішній ітератор у зовнішній.
Це не має відношення до волокон, можливо, але дозвольте зазначити ще одну річ, яку ви можете зробити з Enumerators: вони дозволяють застосовувати численні методи вищого порядку до інших ітераторів, крім 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
метод від 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]
Я не знаю, чи для цього використовуються волокна під кришкою, але це могло б. Волокна можна використовувати для впровадження нескінченних списків та ледачих оцінок серії. Для прикладу деяких ледачих методів, визначених за допомогою Enumerators, я дещо визначив тут: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Ви також можете побудувати споруду загального призначення з використанням волокон. Я ніколи ще не використовував супротивної програми в жодній із моїх програм, але це добре розуміти.
Я сподіваюся, що це дає вам деяке уявлення про можливості. Як я вже говорив на початку, волокна є примітивом низького рівня контролю потоку. Вони дозволяють підтримувати декілька "позицій" у вашій програмі (наприклад, різні "закладки" на сторінках книги) та перемикатися між ними за бажанням. Оскільки довільний код може працювати у волокні, ви можете зателефонувати в сторонній код на волокні, а потім "заморозити" його та продовжувати робити щось інше, коли він передзвонить у керований вами код.
Уявіть щось подібне: ви пишете серверну програму, яка обслуговуватиме багатьох клієнтів. Повна взаємодія з клієнтом передбачає проходження низки кроків, але кожне з'єднання є тимчасовим, і ви повинні пам'ятати стан кожного клієнта між з'єднаннями. (Це схоже на веб-програмування?)
Замість того, щоб явно зберігати цей стан і перевіряти його щоразу, коли клієнт підключається (щоб побачити, що наступний "крок", який він повинен зробити), ви можете підтримувати волокно для кожного клієнта. Визначившись із клієнтом, ви отримаєте його волокно та перезапустите його. Тоді в кінці кожного з'єднання ви призупиняєте волокно і зберігаєте його знову. Таким чином, ви можете написати прямолінійний код, щоб реалізувати всю логіку для повної взаємодії, включаючи всі кроки (так само, як ви, природно, якби ваша програма була запущена локально).
Я впевнений, що є багато причин, чому таке може не бути практичним (принаймні поки що), але знову ж таки я просто намагаюся показати вам деякі можливості. Хто знає; Як тільки ви отримаєте цю концепцію, ви можете придумати якісь абсолютно нові програми, про які ще ніхто не думав!