Eine Coroutine ähnelt einem Thread, sie ist eine Ausführungszeile mit einem eigenen Stapel, eigenen lokalen Variablen und einem eigenen Zeiger für die Anweisungen, jedoch mit der Besonderheit, dass sie die globalen Variablen und jedes andere Element mit den anderen Corutines teilt.
Wir müssen jedoch klarstellen, dass es Unterschiede zwischen Threads und Corrutinas gibt. Der Hauptunterschied besteht darin, dass ein Programm, das Threads verwendet, gleichzeitig ausgeführt wird. Corutinas hingegen sind kollaborativ, während ein Programm, das Corrutinas verwendet, nur eines dieser Programme ausführt Die Aussetzung dieser wird nur auf ausdrücklichen Wunsch erreicht.
Die Corrutinas sind extrem mächtig. Sehen wir uns an, was dieses Konzept beinhaltet und wie wir sie in unseren Programmen verwenden können.
Grundlegende Konzepte
Alle Funktionen, die sich auf Coroutinen in Lua beziehen, befinden sich in der Tabelle corutinas, in der wir sie mit der Funktion create () erstellen können. Sie hat ein einfaches Argument und ist die Funktion mit dem Code, den die Corutina ausführen wird Das Gleiche ist ein Wert des Gewindetyps, der die neue Corrutina darstellt. Sogar das Argument zum Erstellen der Corrutina ist manchmal eine anonyme Funktion wie im folgenden Beispiel:
co = coroutine.create (function () print ("Hallo TechnoWikis") end)
Eine Coroutine kann vier verschiedene Zustände haben:
- ausgesetzt
- Laufen
- tot
- normal
Wenn wir es erstellen, startet es im angehaltenen Zustand, was bedeutet, dass die Corutina beim erstmaligen Erstellen nicht automatisch ausgeführt wird. Wir können den Status einer Corrutina folgendermaßen überprüfen:
drucken (coroutine.status (co))
Wobei wir zur Ausführung unserer Corrutina nur die Funktion resume () verwenden sollten, die intern dazu dient, den Status derselben von suspend in running zu ändern.
coroutine.resume (co)
Wenn wir unseren gesamten Code zusammenfassen und eine zusätzliche Zeile hinzufügen, um den zusätzlichen Status unserer Corrutina nach dem Erstellen einer Zusammenfassung abzufragen, können wir alle Zustände sehen, durch die er geht:
co = coroutine.create (function () print ("Hallo TechnoWikis") end) drucken (co) drucken (coroutine.status (co)) coroutine.resume (co) drucken (coroutine.status (co))
Wir gehen zu unserem Terminal und führen unser Beispiel aus. Sehen wir uns die Ausgabe unseres Programms an:
lua corrutinas1.lua Thread: 0x210d880 Ausgesetzt Hallo TechnoWikis tot
Wie wir sehen können, ist der erste Eindruck von der Korutina der Wert des Threads, dann haben wir den angehaltenen Zustand, und dies ist in Ordnung, da dies der erste Zustand ist, wenn wir die Erstellung durchführen. Dann führen wir mit Resume die Korutina aus, mit der die Nachricht gedruckt wird und danach ist sein Zustand tot , da er seine Mission erfüllt hat.
Coroutinen scheinen auf den ersten Blick eine komplizierte Methode zu sein, um Funktionen aufzurufen, aber sie sind viel komplexer. Die Stärke dieser Funktionen liegt in einem großen Teil der yield () -Funktion, mit der Sie eine ausgeführte Corrutine aussetzen können, um ihre Funktionsweise zusammenzufassen. Sehen wir uns ein Beispiel für die Verwendung dieser Funktion an:
co = coroutine.create (function () für i = 1,10 tun print ("corrutina wieder aufnehmen", i) coroutine.yield () ende Ende) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co)
Diese Funktion wird mit der ersten Ausbeute ausgeführt , und unabhängig davon, ob wir einen for- Zyklus haben, wird nur nach der Anzahl der Summen gedruckt, die wir für unsere Corrutina haben. Schließlich sehen wir die Ausgabe über das Terminal:
lua corrutinas1.lua 1 2 3 4
Dies wäre der Ausgang durch das Terminal.
Filter
Eines der klarsten Beispiele für die Erklärung von Corrutinas ist der Fall des Verbrauchers und des Informationsgenerators. Angenommen, wir haben eine Funktion, die kontinuierlich einige Werte für das Lesen einer Datei generiert, und dann eine andere Funktion, die diese Werte liest. Sehen wir uns ein anschauliches Beispiel an, wie diese Funktionen aussehen könnten:
Generatorfunktion () während wahr zu tun local x = io.read () senden (x) ende ende Verbraucherfunktion () während wahr zu tun local x = receive () io.write (x, " n") ende ende
In diesem Beispiel laufen sowohl der Verbraucher als auch der Generator ohne Pause und wir können sie anhalten, wenn keine weiteren Informationen zu verarbeiten sind. Das Problem hierbei ist jedoch die Art und Weise, wie die Funktionen von send () und receive () synchronisiert werden Einer von ihnen hat eine eigene Schleife, und es wird angenommen, dass der andere ein Dienst ist, der aufgerufen werden kann.
Mit den Corrutinas kann dieses Problem jedoch schnell und einfach gelöst werden. Mit dem Funktionspaar Resume / Yield können wir dafür sorgen, dass unsere Funktionen problemlos funktionieren. Wenn eine Corrutina die Funktion yield aufruft, gibt sie keine neue Funktion ein, sondern gibt einen anstehenden Aufruf zurück, der diesen Zustand nur bei Verwendung von resume verlassen kann.
Wenn ein Resume aufgerufen wird, startet auch keine neue Funktion, sondern es wird ein Wait-to- Yield-Aufruf zurückgegeben . Eine Zusammenfassung dieses Prozesses ist erforderlich, um die Funktionen von send () und receive () zu synchronisieren. Wenn wir diese Operation anwenden, müssten wir ” receive ()” verwenden, um einen Lebenslauf auf den Generator anzuwenden, um die neuen Informationen zu generieren, und dann ” send ()”, um die Rendite auf den Verbraucher anzuwenden. Sehen wir uns an, wie unsere Funktionen mit den neuen Änderungen sind:
Funktion erhalten () Lokaler Status, Wert = coroutine.resume (Generator) Rückgabewert ende Funktion senden (x) coroutine.yield (x) ende gen = coroutine.create ( function () während wahr zu tun local x = io.read () senden (x) ende Ende)
Wir können unser Programm jedoch noch weiter verbessern, und es verwendet die Filter , bei denen es sich um Aufgaben handelt, die gleichzeitig als Erzeuger und Verbraucher fungieren, was einen Transformationsprozess der Informationen sehr interessant macht.
Ein Filter kann einen Generator zusammenfassen, um neue Werte zu erhalten, und dann den Ertrag anwenden , um die Daten für den Verbraucher zu transformieren. Lassen Sie uns sehen, wie wir die Filter auf einfache Weise zu unserem vorherigen Beispiel hinzufügen können:
Gen = Generator () fil = Filter (Gen) Verbraucher (fil)
Aus unserer Sicht war es sehr einfach, nicht nur unser Programm zu optimieren, sondern auch die Lesbarkeit zu verbessern, die für die zukünftige Wartung wichtig ist.
Corrutines als Iteratoren
Eines der klarsten Beispiele für den Generator / Konsumenten sind die in den rekursiven Zyklen vorhandenen Iteratoren , bei denen ein Iterator Informationen generiert, die vom Körper innerhalb des rekursiven Zyklus verbraucht werden. Daher wäre es nicht unangemessen, diese Iteratoren mit Corutinas zu schreiben. Auch Koroutinen haben ein spezielles Werkzeug für diese Aufgabe.
Um zu veranschaulichen, wie wir Coroutinen verwenden können , schreiben wir einen Iterator, um die Permutationen eines bestimmten Arrays zu generieren. Platzieren Sie also jedes Element eines Arrays an der letzten Position, drehen Sie es um und generieren Sie dann rekursiv alle Permutationen von Die restlichen Elemente sehen wir uns an, wie unsere ursprüngliche Funktion aussehen würde, ohne Corutinas einzuschließen:
Funktion print_result (var) für i = 1 #var do io.write (var [i], "") ende io.write (" n") ende
Jetzt ändern wir diesen Prozess komplett. Zuerst ändern wir das print_result () nach Ausbeute. Sehen wir uns die Änderung an:
Permgen-Funktion (var1, var2) var2 = var2 oder # var1 wenn var2 <= 1 dann coroutine.yield (var1) sonst
Dies ist ein anschauliches Beispiel zur Veranschaulichung der Funktionsweise der Iteratoren. Lua stellt jedoch eine Funktion namens wrap zur Verfügung , die der Funktion create ähnlich ist. Es wird jedoch keine Corutina zurückgegeben, sondern eine Funktion, die eine Corrutina zusammenfasst. Um Wrap zu verwenden, sollten wir nur Folgendes verwenden:
Funktionspermutationen (var) Rückgabe coroutine.wrap (function () permgen (var) end) ende
Normalerweise ist diese Funktion viel einfacher zu verwenden als create , da sie genau das liefert, was wir brauchen, dh zusammenfasst. Sie ist jedoch weniger flexibel, da wir den Status der mit wrap erstellten Coroutine nicht überprüfen können.
Die Corrutinas in Lua sind ein äußerst leistungsfähiges Werkzeug, um alles zu erledigen, was mit Prozessen zusammenhängt, die Hand in Hand ausgeführt werden müssen. Sie warten jedoch auf die Vervollständigung desjenigen, der die Informationen bereitstellt, und wir könnten sehen, wie sie zur Lösung komplexer Probleme in Bezug auf Prozesse von eingesetzt werden Generator / Verbraucher und auch die Konstruktion von Iteratoren in unseren Programmen zu optimieren.