Dla Fibers mamy klasyczny przykład: generowanie liczb Fibonacciego
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y,x+y
end
end
Dlaczego potrzebujemy tutaj włókien? Mogę to przepisać z tym samym procesem (właściwie zamknięciem)
def clsr
x, y = 0, 1
Proc.new do
x, y = y, x + y
x
end
end
Więc
10.times { puts fib.resume }
i
prc = clsr
10.times { puts prc.call }
zwróci ten sam wynik.
Więc jakie są zalety włókien. Jakie rzeczy mogę napisać za pomocą Fibers, czego nie mogę zrobić z lambdami i innymi fajnymi funkcjami Rubiego?
Odpowiedzi:
Włókna to coś, czego prawdopodobnie nigdy nie użyjesz bezpośrednio w kodzie na poziomie aplikacji. Są to prymityw do sterowania przepływem, którego można użyć do tworzenia innych abstrakcji, których można następnie używać w kodzie wyższego poziomu.
Prawdopodobnie pierwszym zastosowaniem włókien w Rubim jest implementacja
Enumerator
s, które są podstawową klasą Rubiego w Rubim 1.9. Są niezwykle przydatne.W Rubim 1.9, jeśli wywołasz prawie każdą metodę iteratora na klasach rdzenia, bez przekazywania bloku, zwróci to plik
Enumerator
.Są
Enumerator
to obiekty Enumerable, a icheach
metody dają elementy, które zostałyby uzyskane przez oryginalną metodę iteratora, gdyby została wywołana z blokiem. W podanym przeze mnie przykładzie Enumerator zwrócony przezreverse_each
maeach
metodę, która zwraca 3,2,1. Enumerator zwrócony przezchars
zwraca „c”, „b”, „a” (i tak dalej). ALE, w przeciwieństwie do oryginalnej metody iteratora, Enumerator może również zwracać elementy jeden po drugim, jeśli wywołujesznext
go wielokrotnie:Być może słyszałeś o „wewnętrznych iteratorach” i „zewnętrznych iteratorach” (dobry opis obu znajduje się w książce „Gang of Four” Design Patterns). Powyższy przykład pokazuje, że Enumerators mogą służyć do przekształcania wewnętrznego iteratora w zewnętrzny.
Oto jeden ze sposobów tworzenia własnych modułów wyliczających:
Spróbujmy:
Chwileczkę ... czy coś tam wydaje się dziwne? Napisałeś
yield
instrukcjean_iterator
jako kod liniowy, ale moduł wyliczający może uruchamiać je pojedynczo . W międzyczasienext
wykonanie poleceniaan_iterator
jest „zawieszane”. Za każdym razem, gdy dzwonisznext
, przechodzi do następnejyield
instrukcji, a następnie ponownie „zawiesza się”.Czy możesz zgadnąć, jak to jest realizowane? Enumerator zawija wywołanie do
an_iterator
światłowodu i przekazuje blok, który zawiesza światłowód . Tak więc za każdym razem, gdyan_iterator
ustępuje blokowi, światłowód, na którym działa, jest zawieszany, a wykonywanie jest kontynuowane w głównym wątku. Następnym razem, gdy dzwonisznext
, przekazuje sterowanie do światłowodu, blok wraca ian_iterator
kontynuuje od miejsca, w którym został przerwany.Pouczające byłoby zastanowienie się, co byłoby potrzebne do zrobienia tego bez włókien. KAŻDA klasa, która chciała udostępniać zarówno wewnętrzne, jak i zewnętrzne iteratory, musiałaby zawierać jawny kod, aby śledzić stan między wywołaniami
next
. Każde wywołanie next musiałoby sprawdzić ten stan i zaktualizować go przed zwróceniem wartości. Dzięki światłowodom możemy automatycznie przekształcić dowolny wewnętrzny iterator na zewnętrzny.Nie ma to nic wspólnego z włóknami, ale wspomnę jeszcze o jednej rzeczy, którą możesz zrobić z Enumeratorami: pozwalają one na zastosowanie metod Enumerable wyższego rzędu do innych iteratorów innych niż
each
. Pomyśl o tym: normalnie wszystkie przeliczalne metody, w tymmap
,select
,include?
,inject
, i tak dalej, wszystkie prace na elementach uzyskano przezeach
. Ale co, jeśli obiekt ma inne iteratory inne niżeach
?Wywołanie iteratora bez bloku zwraca Enumerator, a następnie możesz wywołać inne metody Enumerable.
Wracając do włókien, czy użyłeś
take
metody z Enumerable?Jeśli cokolwiek wywołuje tę
each
metodę, wygląda na to, że nigdy nie powinna powrócić, prawda? Sprawdź to:Nie wiem, czy to wykorzystuje włókna pod maską, ale mogłoby. Włókna mogą służyć do implementacji nieskończonych list i leniwej oceny serii. Na przykład niektóre leniwe metody zdefiniowane w Enumerators, zdefiniowałem tutaj: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Możesz również zbudować ośrodek ogólnego przeznaczenia z użyciem włókien. Nigdy jeszcze nie używałem programów w żadnym z moich programów, ale warto wiedzieć.
Mam nadzieję, że to daje ci wyobrażenie o możliwościach. Jak powiedziałem na początku, włókna są prymitywem kontroli przepływu niskiego poziomu. Umożliwiają one utrzymywanie wielu „pozycji” przepływu sterowania w programie (jak różne „zakładki” na stronach książki) i przełączanie się między nimi w razie potrzeby. Ponieważ dowolny kod może działać w światłowodzie, możesz wywołać kod strony trzeciej na światłowodzie, a następnie „zamrozić” go i kontynuować wykonywanie innych czynności, gdy wywoła kod, który kontrolujesz.
Wyobraź sobie coś takiego: piszesz program serwera, który będzie obsługiwał wielu klientów. Pełna interakcja z klientem wymaga wykonania szeregu kroków, ale każde połączenie jest przejściowe i należy pamiętać stan każdego klienta między połączeniami. (Brzmi jak programowanie internetowe?)
Zamiast jawnie zapisywać ten stan i sprawdzać go za każdym razem, gdy klient się łączy (aby zobaczyć, jaki będzie następny „krok”, jaki musi wykonać), można zachować światłowód dla każdego klienta. Po zidentyfikowaniu klienta należy odzyskać jego włókno i ponownie go uruchomić. Następnie na końcu każdego połączenia zawieszasz światłowód i przechowujesz go ponownie. W ten sposób możesz napisać kod w linii prostej, aby zaimplementować całą logikę dla pełnej interakcji, w tym wszystkie kroki (tak jak naturalnie byś zrobił, gdyby twój program był uruchamiany lokalnie).
Jestem pewien, że istnieje wiele powodów, dla których taka rzecz może nie być praktyczna (przynajmniej na razie), ale znowu próbuję tylko pokazać niektóre możliwości. Kto wie; kiedy już zdobędziesz koncepcję, możesz wymyślić zupełnie nową aplikację, o której nikt inny jeszcze nie pomyślał!
źródło
chars
ani innych modułów wyliczających z samymi zamknięciami?Enumerable
Ruby 2.0 będzie zawierał pewne "leniwe" metody.take
nie wymaga błonnika. Zamiast tegotake
po prostu pęka podczas n-tego plonu. Gdy jest używany wewnątrz bloku,break
zwraca sterowanie do ramki definiującej blok.a = [] ; InfiniteSeries.new.each { |x| a << x ; break if a.length == 10 } ; a
W przeciwieństwie do zamknięć, które mają określony punkt wejścia i wyjścia, włókna mogą wielokrotnie zachowywać swój stan i zwrot (plastyczność):
drukuje to:
Implementacja tej logiki z innymi funkcjami ruby będzie mniej czytelna.
Dzięki tej funkcji dobrym wykorzystaniem włókien jest ręczne planowanie współpracy (jako wymiana wątków). Ilya Grigorik ma dobry przykład tego, jak zamienić bibliotekę asynchroniczną (
eventmachine
w tym przypadku) w coś, co wygląda jak synchroniczny interfejs API, bez utraty zalet planowania we / wy wykonywania asynchronicznego. Oto link .źródło
physical meaning
w prostszym przykładzie