EIN coroutine Es ähnelt einem Thread, es ist eine Ausführungszeile mit eigenem Stack, eigenen lokalen Variablen und eigenem Zeiger für die Anweisungen, jedoch mit der Besonderheit, dass sie globale Variablen und jedes andere Element mit den anderen Coroutinen teilt.
Aber wir müssen klarstellen, dass es Unterschiede zwischen den Fäden und das Koroutinen, der Hauptunterschied besteht darin, dass ein Programm, das Threads verwendet, diese gleichzeitig ausführt Koroutinen auf der anderen Seite sind sie kollaborativ, wobei ein Programm, das Coroutinen verwendet, nur eine davon ausführt und deren Aussetzung nur erreicht wird, wenn dies ausdrücklich angefordert wird.
Das Koroutinen Sie sind extrem mächtig. Sehen wir uns an, was dieses Konzept umfasst und wie wir sie in unseren Programmen verwenden können.
Grundlegendes Konzept
Alle Funktionen zu Coroutinen in Lua finden sich in der Coroutinen-Tabelle, in der die Funktion schaffen () ermöglicht es uns, sie zu erstellen, hat ein einfaches Argument und ist die Funktion mit dem Code, die die Coroutine ausführen wird, wobei ihre Rückgabe ein Wert des Thread-Typs ist, der die neue Coroutine darstellt. Sogar das Argument zum Erstellen der Coroutine ist manchmal eine anonyme Funktion, wie im folgenden Beispiel:
co = coroutine.create (function () print ("Hello Solvetic") end)EIN coroutine Es kann vier verschiedene Zustände haben:
- ausgesetzt
- in Eile
- tot
- normal
Wenn wir es erstellen, beginnt es im Zustand abgesetzt, was bedeutet, dass die Coroutine nicht automatisch ausgeführt wird, wenn sie zum ersten Mal erstellt wird. Der Status einer Coroutine kann wie folgt abgerufen werden:
drucken (coroutine.status (co))Um unsere Coroutine ausführen zu können, brauchen wir nur die Funktion von fasst zusammen(), was es intern tut, ist, seinen Status von suspendiert in Running zu ändern.
coroutine.resume (co)Wenn wir unseren gesamten Code zusammenfügen und eine zusätzliche Zeile hinzufügen, um den zusätzlichen Status unserer Coroutine nach dem Ausführen abzufragen fasst zusammen wir können alle Zustände sehen, die es durchläuft:
co = coroutine.create (function () print ("Hello Solvetic") end) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))Wir gehen zu unserem Terminal und führen unser Beispiel aus, sehen wir uns die Ausgabe unseres Programms an:
lua coroutines1.lua-Thread: 0x210d880 Suspended Hello Solvetic deadWie wir sehen, ist der erste Eindruck der Coroutine der Wert des Threads, dann haben wir den Zustand ausgesetzt, und das ist in Ordnung, da dies der erste Zustand beim Erstellen ist, dann mit fasst zusammen Wir führen die Coroutine aus, mit der sie die Nachricht ausgibt, und danach ist ihr Status totals es seinen Auftrag erfüllte.
Coroutinen mögen auf den ersten Blick wie eine komplizierte Art erscheinen, Funktionen aufzurufen, sie sind jedoch viel komplexer. Die Macht derselben liegt in einem großen Teil der Funktion Ertrag () die es ermöglicht, eine laufende Coroutine anzuhalten, um ihren Betrieb später wieder aufzunehmen, sehen wir uns ein Beispiel für die Verwendung dieser Funktion an:
co = coroutine.create (function () for i = 1.10 do print ("Zusammenfassung von coroutine", i) coroutine.yield () end end) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co ) coroutine .Lebenslauf (co)Diese Funktion wird bis zum ersten ausgeführt Ertrag, und unabhängig davon, ob wir einen Zyklus haben Pro, es wird nur nach so vielen gedruckt fasst zusammen Lassen Sie uns für unsere Coroutine zum Abschluss die Ausgabe über das Terminal sehen:
lua-Koroutinen 1. lua 1 2 3 4Dies wäre der Ausgang durch das Terminal.
Filter
Eines der klarsten Beispiele zur Erklärung von Coroutinen ist der Fall von Verbraucher Ja Generator von Informationen. Angenommen, wir haben eine Funktion, die kontinuierlich einige Werte aus dem Lesen einer Datei generiert, und dann haben wir eine andere Funktion, die diese liest. Sehen wir uns ein anschauliches Beispiel an, wie diese Funktionen aussehen könnten:
Funktionsgenerator () while true do local x = io.read () send (x) end end function Consumer () while true do local x = Receive () io.write (x, "\ n") end endIn diesem Beispiel laufen sowohl der Verbraucher als auch der Generator ohne jede Art von Ruhe und wir können sie stoppen, wenn keine Informationen mehr zu verarbeiten sind Senden() Ja erhalten(), da jeder von ihnen seine eigene Schleife hat und der andere als aufrufbarer Dienst angenommen wird.
Aber mit den Coroutinen lässt sich dieses Problem schnell und einfach mit der Double-Funktion lösen Lebenslauf / Ertrag Wir können unsere Funktionen problemlos zum Laufen bringen. Wenn eine Coroutine die Funktion aufruft Ertrag, gibt es keine neue Funktion ein, sondern gibt einen anstehenden Aufruf zurück, der diesen Zustand nur durch Verwenden von "Resume" verlassen kann.
Ebenso beim Anrufen fasst zusammen startet auch keine neue Funktion, sondern gibt einen Wait-Aufruf an . zurück ErtragZusammenfassend ist dieser Prozess der, den wir brauchen, um die Funktionen von . zu synchronisieren Senden() Ja erhalten(). Anwenden dieser Operation müssten wir verwenden erhalten() Anwenden fasst zusammen an den Generator, um die neuen Informationen zu generieren und dann Senden() sich bewerben Ertrag Sehen wir uns für den Verbraucher an, wie unsere Funktionen mit den neuen Änderungen aussehen:
function receive () local status, value = coroutine.resume (generator) Rückgabewert end function send (x) coroutine.yield (x) end gen = coroutine.create (function () while true do local x = io.read () senden (x) Ende Ende)Aber wir können unser Programm noch weiter verbessern, und zwar durch die Nutzung der Filter, das sind Aufgaben, die gleichzeitig als Erzeuger und Verbraucher fungieren und einen sehr interessanten Prozess der Informationstransformation darstellen.
EIN Filter tun können fasst zusammen von einem Generator, um neue Werte zu erhalten und dann anzuwenden Ertrag um Daten für den Verbraucher zu transformieren. Sehen wir uns an, wie wir unserem vorherigen Beispiel ganz einfach Filter hinzufügen können:
gen = Generator () fil = Filter (gen) Verbraucher (fil)Wie wir sehen, war es extrem einfach, wo wir neben der Optimierung unseres Programms auch Punkte in der Lesbarkeit gewonnen haben, die für die zukünftige Wartung wichtig sind.
Corroutinen als Iteratoren
Eines der deutlichsten Beispiele für den Erzeuger/Verbraucher ist der Iteratoren in rekursiven Zyklen vorhanden sind, bei denen ein Iterator Informationen generiert, die vom Körper innerhalb des rekursiven Zyklus konsumiert werden, so dass es nicht unvernünftig wäre, Coroutinen zum Schreiben dieser Iteratoren zu verwenden.
Um die Verwendung zu veranschaulichen, die wir machen können Koroutinen, werden wir einen Iterator schreiben, um die Permutationen eines gegebenen Arrays zu generieren, d. h. jedes Element eines Arrays an der letzten Position platzieren und umdrehen und dann rekursiv alle Permutationen der verbleibenden Elemente generieren Die ursprüngliche Funktion wäre ohne die Coroutinen:
Funktion print_result (var) für i = 1, #var do io.write (var [i], "") end io.write ("\ n") endJetzt ändern wir diesen Prozess komplett, zuerst ändern wir die print_result () nach Ertrag, sehen wir uns die Änderung an:
Funktion permgen (var1, var2) var2 = var2 oder # var1 wenn var2 <= 1 dann coroutine.yield (var1) sonstDies ist jedoch ein anschauliches Beispiel, um zu demonstrieren, wie Iteratoren funktionieren Lua liefert uns eine Funktion namens wickeln was ähnlich ist schaffenEs gibt jedoch keine Coroutine zurück, sondern eine Funktion, die beim Aufrufen eine Coroutine zusammenfasst. Dann zu verwenden wickeln Wir sollten nur Folgendes verwenden:
Funktionspermutationen (var) return coroutine.wrap (function () permgen (var) end) endNormalerweise ist diese Funktion viel einfacher zu bedienen als schaffen, da es uns genau das gibt, was wir brauchen, also zusammenfassend, aber es ist weniger flexibel, da es uns nicht erlaubt, den Status der mit erstellten Coroutine zu überprüfen wickeln.
Die Koroutinen in Lua Sie sind ein äußerst leistungsfähiges Werkzeug, um alles zu handhaben, was mit Prozessen zu tun hat, die Hand in Hand ausgeführt werden müssen, aber auf den Abschluss desjenigen warten, der die Informationen bereitstellt, können wir auch ihre Verwendung zur Lösung komplexer Probleme in Bezug auf Generator- / Verbraucherprozesse sehen und auch die Optimierung des Baus von Iteratoren in unseren Programmen.