Pour les fibres, nous avons un exemple classique: la génération de nombres de Fibonacci
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y,x+y
end
end
Pourquoi avons-nous besoin de fibres ici? Je peux réécrire cela avec juste le même Proc (fermeture, en fait)
def clsr
x, y = 0, 1
Proc.new do
x, y = y, x + y
x
end
end
Alors
10.times { puts fib.resume }
et
prc = clsr
10.times { puts prc.call }
retournera juste le même résultat.
Alors, quels sont les avantages des fibres. Quel genre de trucs que je peux écrire avec Fibers que je ne peux pas faire avec les lambdas et autres fonctionnalités intéressantes de Ruby?
Réponses:
Les fibres sont quelque chose que vous n'utiliserez probablement jamais directement dans le code au niveau de l'application. Il s'agit d'une primitive de contrôle de flux que vous pouvez utiliser pour créer d'autres abstractions, que vous utilisez ensuite dans un code de niveau supérieur.
L'utilisation n ° 1 des fibres dans Ruby est probablement d'implémenter
Enumerator
s, qui sont une classe Ruby de base dans Ruby 1.9. Ce sont incroyablement utiles.Dans Ruby 1.9, si vous appelez presque n'importe quelle méthode d'itérateur sur les classes principales, sans passer un bloc, elle renverra un
Enumerator
.Ce
Enumerator
sont des objets Enumerable, et leurseach
méthodes donnent les éléments qui auraient été générés par la méthode iterator originale, si elle avait été appelée avec un bloc. Dans l'exemple que je viens de donner, l'énumérateur renvoyé parreverse_each
a uneeach
méthode qui donne 3,2,1. L'énumérateur renvoyé par renvoiechars
"c", "b", "a" (et ainsi de suite). MAIS, contrairement à la méthode iterator d'origine, l'énumérateur peut également retourner les éléments un par un si vous l'appeleznext
à plusieurs reprises:Vous avez peut-être entendu parler des «itérateurs internes» et des «itérateurs externes» (une bonne description des deux est donnée dans le livre «Gang of Four» Design Patterns). L'exemple ci-dessus montre que les énumérateurs peuvent être utilisés pour transformer un itérateur interne en un itérateur externe.
Voici une façon de créer vos propres recenseurs:
Essayons:
Attendez une minute ... est-ce que quelque chose vous semble étrange? Vous avez écrit les
yield
instructions sousan_iterator
forme de code linéaire, mais l'énumérateur peut les exécuter une par une . Entre les appels ànext
, l'exécution dean_iterator
est "gelée". Chaque fois que vous appeleznext
, il continue de fonctionner jusqu'à l'yield
instruction suivante , puis "se fige" à nouveau.Pouvez-vous deviner comment cela est mis en œuvre? L'énumérateur encapsule l'appel
an_iterator
dans une fibre et transmet un bloc qui suspend la fibre . Ainsi, chaque fois quean_iterator
le bloc cède, la fibre sur laquelle il fonctionne est suspendue et l'exécution se poursuit sur le thread principal. La prochaine fois que vous appeleznext
, il passe le contrôle à la fibre, le bloc revient etan_iterator
continue là où il s'était arrêté.Il serait instructif de penser à ce qu'il faudrait pour faire cela sans fibres. CHAQUE classe qui voulait fournir des itérateurs internes et externes devrait contenir du code explicite pour garder une trace de l'état entre les appels à
next
. Chaque appel à next devrait vérifier cet état et le mettre à jour avant de renvoyer une valeur. Avec les fibres, nous pouvons convertir automatiquement n'importe quel itérateur interne en un itérateur externe.Cela n'a pas à voir avec les fibres persay, mais permettez-moi de mentionner une autre chose que vous pouvez faire avec les Enumerators: ils vous permettent d'appliquer des méthodes Enumerable d'ordre supérieur à d'autres itérateurs autres que
each
. Pensez-y: normalement toutes les méthodes, y compris Enumerablemap
,select
,include?
,inject
et ainsi de suite, tous les travaux sur les éléments produits pareach
. Mais que faire si un objet a d'autres itérateurs autres queeach
?L'appel de l'itérateur sans bloc renvoie un Enumerator, puis vous pouvez appeler d'autres méthodes Enumerable à ce sujet.
Pour en revenir aux fibres, avez-vous utilisé la
take
méthode d'Enumerable?Si quelque chose appelle cette
each
méthode, il semble qu'elle ne devrait jamais revenir, non? Regarde ça:Je ne sais pas si cela utilise des fibres sous le capot, mais c'est possible. Les fibres peuvent être utilisées pour implémenter des listes infinies et une évaluation paresseuse d'une série. Pour un exemple de certaines méthodes paresseuses définies avec Enumerators, j'en ai défini quelques-unes ici: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Vous pouvez également créer une installation de coroutine à usage général en utilisant des fibres. Je n'ai encore jamais utilisé de coroutines dans aucun de mes programmes, mais c'est un bon concept à connaître.
J'espère que cela vous donne une idée des possibilités. Comme je l'ai dit au début, les fibres sont une primitive de contrôle de flux de bas niveau. Ils permettent de maintenir plusieurs «positions» de flux de contrôle au sein de votre programme (comme différents «signets» dans les pages d'un livre) et de basculer entre elles à volonté. Puisque du code arbitraire peut s'exécuter dans une fibre, vous pouvez appeler du code tiers sur une fibre, puis le «figer» et continuer à faire autre chose lorsqu'il rappelle le code que vous contrôlez.
Imaginez quelque chose comme ceci: vous écrivez un programme serveur qui desservira de nombreux clients. Une interaction complète avec un client implique de passer par une série d'étapes, mais chaque connexion est transitoire et vous devez vous souvenir de l'état de chaque client entre les connexions. (Cela ressemble à de la programmation Web?)
Plutôt que de stocker explicitement cet état et de le vérifier à chaque fois qu'un client se connecte (pour voir quelle est la prochaine "étape" qu'il doit faire), vous pouvez maintenir une fibre pour chaque client. Après avoir identifié le client, vous récupéreriez sa fibre et la redémarreriez. Ensuite, à la fin de chaque connexion, vous suspendriez la fibre et la stockiez à nouveau. De cette façon, vous pouvez écrire du code en ligne droite pour implémenter toute la logique d'une interaction complète, y compris toutes les étapes (comme vous le feriez naturellement si votre programme était conçu pour s'exécuter localement).
Je suis sûr qu'il y a de nombreuses raisons pour lesquelles une telle chose n'est peut-être pas pratique (du moins pour le moment), mais encore une fois, j'essaie simplement de vous montrer certaines des possibilités. Qui sait; une fois que vous avez compris le concept, vous pouvez créer une application totalement nouvelle à laquelle personne d'autre n'a encore pensé!
la source
chars
ou d'autres agents recenseurs avec juste des fermetures?Enumerable
certaines méthodes "paresseuses" soient incluses dans Ruby 2.0.take
ne nécessite pas de fibre. Au lieu de cela,take
se casse simplement pendant le nième rendement. Lorsqu'il est utilisé à l'intérieur d'un bloc,break
renvoie le contrôle au cadre définissant le bloc.a = [] ; InfiniteSeries.new.each { |x| a << x ; break if a.length == 10 } ; a
Contrairement aux fermetures, qui ont un point d'entrée et de sortie défini, les fibres peuvent conserver leur état et revenir (céder) plusieurs fois:
imprime ceci:
L'implémentation de cette logique avec d'autres fonctionnalités ruby sera moins lisible.
Avec cette fonctionnalité, une bonne utilisation des fibres consiste à effectuer une planification coopérative manuelle (en remplacement des threads). Ilya Grigorik a un bon exemple sur la façon de transformer une bibliothèque asynchrone (
eventmachine
dans ce cas) en ce qui ressemble à une API synchrone sans perdre les avantages de la planification IO de l'exécution asynchrone. Voici le lien .la source
physical meaning
dans un exemple plus simple