Für Fasern haben wir ein klassisches Beispiel: Generieren von Fibonacci-Zahlen
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y,x+y
end
end
Warum brauchen wir hier Fasern? Ich kann dies mit genau dem gleichen Proc umschreiben (Abschluss eigentlich)
def clsr
x, y = 0, 1
Proc.new do
x, y = y, x + y
x
end
end
So
10.times { puts fib.resume }
und
prc = clsr
10.times { puts prc.call }
gibt genau das gleiche Ergebnis zurück.
Was sind die Vorteile von Fasern? Was für Sachen kann ich mit Fasern schreiben, die ich mit Lambdas und anderen coolen Ruby-Funktionen nicht machen kann?
Antworten:
Fasern werden Sie wahrscheinlich nie direkt im Code auf Anwendungsebene verwenden. Sie sind ein Flusssteuerungsprimitiv, mit dem Sie andere Abstraktionen erstellen können, die Sie dann in übergeordnetem Code verwenden.
Wahrscheinlich besteht die häufigste Verwendung von Fasern in Ruby darin,
Enumerator
s zu implementieren , die eine Ruby-Kernklasse in Ruby 1.9 sind. Diese sind unglaublich nützlich.Wenn Sie in Ruby 1.9 fast jede Iteratormethode für die Kernklassen aufrufen, ohne einen Block zu übergeben, wird eine zurückgegeben
Enumerator
.Dies
Enumerator
sind Enumerable-Objekte, und ihreeach
Methoden liefern die Elemente, die von der ursprünglichen Iterator-Methode erhalten worden wären, wenn sie mit einem Block aufgerufen worden wäre. In dem Beispiel, das ich gerade gegeben habe, hat der von zurückgegebene Enumeratorreverse_each
eineeach
Methode, die 3,2,1 ergibt. Der Enumerator gab diechars
Ausbeuten "c", "b", "a" (und so weiter) zurück. ABER im Gegensatz zur ursprünglichen Iteratormethode kann der Enumerator die Elemente auch einzeln zurückgeben, wenn Sienext
sie wiederholt aufrufen :Möglicherweise haben Sie von "internen Iteratoren" und "externen Iteratoren" gehört (eine gute Beschreibung von beiden finden Sie im Buch "Gang of Four" -Designmuster). Das obige Beispiel zeigt, dass Enumeratoren verwendet werden können, um einen internen Iterator in einen externen zu verwandeln.
Dies ist eine Möglichkeit, eigene Enumeratoren zu erstellen:
Lass es uns versuchen:
Moment mal ... scheint da etwas seltsam? Sie schrieb die
yield
Aussagen inan_iterator
als geradlinigen Code, aber der Enumerator kann sie läuft einen nach dem anderen . Zwischen den Aufrufen vonnext
ist die Ausführung vonan_iterator
"eingefroren". Jedes Malnext
, wenn Sie aufrufen , wird die folgendeyield
Anweisung ausgeführt und "friert" erneut ein.Können Sie sich vorstellen, wie dies umgesetzt wird? Der Enumerator umschließt den Aufruf mit
an_iterator
einer Faser und übergibt einen Block, der die Faser unterbricht . Jedes Mal, wennan_iterator
der Block nachgibt, wird die Faser, auf der er ausgeführt wird, angehalten und die Ausführung im Hauptthread fortgesetzt. Beim nächsten Aufrufnext
wird die Steuerung an die Glasfaser übergeben, der Block kehrt zurück und wird dortan_iterator
fortgesetzt, wo er aufgehört hat.Es wäre lehrreich darüber nachzudenken, was erforderlich wäre, um dies ohne Fasern zu tun. JEDE Klasse, die sowohl interne als auch externe Iteratoren bereitstellen wollte, musste expliziten Code enthalten, um den Status zwischen den Aufrufen von zu verfolgen
next
. Jeder Aufruf zum nächsten müsste diesen Status überprüfen und aktualisieren, bevor ein Wert zurückgegeben wird. Mit Fasern können wir jeden internen Iterator automatisch in einen externen konvertieren.Dies hat nichts mit Fasern zu tun, aber ich möchte noch eines erwähnen, das Sie mit Enumeratoren tun können: Sie ermöglichen es Ihnen, Enumerable-Methoden höherer Ordnung auf andere Iteratoren als anzuwenden
each
. Denken Sie daran: normalerweise alle Enumerable - Methoden, einschließlichmap
,select
,include?
,inject
, und so weiter, alle Arbeiten an den Elementen erhalten durcheach
. Aber was ist, wenn ein Objekt andere Iteratoren als hateach
?Wenn Sie den Iterator ohne Block aufrufen, wird ein Enumerator zurückgegeben. Anschließend können Sie andere Enumerable-Methoden aufrufen.
Zurück zu den Fasern, haben Sie die
take
Methode von Enumerable verwendet?Wenn irgendetwas diese
each
Methode aufruft, sieht es so aus, als sollte sie niemals zurückkehren, oder? Überprüfen Sie dies heraus:Ich weiß nicht, ob hier Fasern unter der Haube verwendet werden, aber es könnte sein. Fasern können verwendet werden, um unendliche Listen und eine verzögerte Auswertung einer Reihe zu implementieren. Als Beispiel für einige mit Enumerators definierte Lazy-Methoden habe ich hier einige definiert: https://github.com/alexdowad/showcase/blob/master/ruby-core/collections.rb
Sie können auch eine Allzweck-Coroutine-Anlage aus Fasern bauen. Ich habe noch nie Coroutinen in einem meiner Programme verwendet, aber es ist ein gutes Konzept zu wissen.
Ich hoffe, dies gibt Ihnen eine Vorstellung von den Möglichkeiten. Wie ich zu Beginn sagte, sind Fasern ein Grundelement für die Flusskontrolle auf niedriger Ebene. Sie ermöglichen es, mehrere Kontrollfluss- "Positionen" in Ihrem Programm beizubehalten (wie verschiedene "Lesezeichen" auf den Seiten eines Buches) und nach Bedarf zwischen ihnen zu wechseln. Da beliebiger Code in einer Glasfaser ausgeführt werden kann, können Sie Code von Drittanbietern auf einer Glasfaser aufrufen, ihn dann "einfrieren" und etwas anderes tun, wenn er den von Ihnen kontrollierten Code zurückruft.
Stellen Sie sich so etwas vor: Sie schreiben ein Serverprogramm, das viele Clients bedient. Eine vollständige Interaktion mit einem Client umfasst eine Reihe von Schritten, aber jede Verbindung ist vorübergehend, und Sie müssen sich den Status für jeden Client zwischen den Verbindungen merken. (Klingt nach Webprogrammierung?)
Anstatt diesen Status explizit zu speichern und jedes Mal zu überprüfen, wenn ein Client eine Verbindung herstellt (um zu sehen, was der nächste "Schritt" ist, den er tun muss), können Sie für jeden Client eine Glasfaser verwalten. Nachdem Sie den Client identifiziert haben, rufen Sie dessen Glasfaser ab und starten ihn neu. Am Ende jeder Verbindung würden Sie die Glasfaser aussetzen und erneut speichern. Auf diese Weise können Sie geradlinigen Code schreiben, um die gesamte Logik für eine vollständige Interaktion einschließlich aller Schritte zu implementieren (genau wie Sie es natürlich tun würden, wenn Ihr Programm lokal ausgeführt würde).
Ich bin mir sicher, dass es viele Gründe gibt, warum so etwas (zumindest für den Moment) nicht praktikabel ist, aber ich versuche nur, Ihnen einige der Möglichkeiten aufzuzeigen. Wer weiß; Sobald Sie das Konzept erhalten haben, können Sie sich eine völlig neue Anwendung einfallen lassen, an die noch niemand gedacht hat!
quelle
chars
anderen Enumeratoren mit nur Abschlüssen?Enumerable
als würde Ruby 2.0 einige "faule" Methoden enthalten.take
benötigt keine Faser. Bricht stattdessentake
einfach während der n-ten Ausbeute. Bei Verwendung innerhalb eines Blocks wirdbreak
die Kontrolle an den Frame zurückgegeben, der den Block definiert.a = [] ; InfiniteSeries.new.each { |x| a << x ; break if a.length == 10 } ; a
Im Gegensatz zu Verschlüssen, die einen definierten Eintritts- und Austrittspunkt haben, können Fasern ihren Zustand beibehalten und viele Male zurückkehren (nachgeben):
druckt dies:
Die Implementierung dieser Logik mit anderen Ruby-Funktionen ist weniger lesbar.
Mit dieser Funktion ist eine gute Fasernutzung die manuelle kooperative Planung (als Thread-Ersatz). Ilya Grigorik hat ein gutes Beispiel dafür, wie eine asynchrone Bibliothek (
eventmachine
in diesem Fall) in eine synchrone API umgewandelt werden kann, ohne die Vorteile der E / A-Planung der asynchronen Ausführung zu verlieren. Hier ist der Link .quelle
physical meaning
in einem einfacheren Beispiel verstehen