EEN coroutine Het is vergelijkbaar met een thread, het is een uitvoeringsregel met zijn eigen stapel, zijn eigen lokale variabelen en zijn eigen aanwijzer voor de instructies, maar met de bijzonderheid dat het globale variabelen en elk ander element deelt met de andere coroutines.
Maar we moeten duidelijk maken dat er verschillen zijn tussen de draden en de coroutines, het belangrijkste verschil is dat een programma dat threads gebruikt deze gelijktijdig uitvoert, de coroutines aan de andere kant zijn ze samenwerkend, waarbij een programma dat coroutines gebruikt slechts één van deze uitvoert, en de opschorting hiervan alleen wordt bereikt als hier expliciet om wordt gevraagd.
De coroutines Ze zijn extreem krachtig, laten we eens kijken wat dit concept omvat en hoe we ze in onze programma's kunnen gebruiken.
Basisconcepten
Alle functies met betrekking tot coroutines in Lua zijn te vinden in de coroutinetabel, waar de functie creëren () stelt ons in staat om ze te maken, het heeft een eenvoudig argument en is de functie met de code die de coroutine zal uitvoeren, waarbij de terugkeer een waarde is van het threadtype, dat de nieuwe coroutine vertegenwoordigt. Zelfs het argument om de coroutine te maken is soms een anonieme functie, zoals in het volgende voorbeeld:
co = coroutine.create (functie () print ("Hallo Solvetic") einde)EEN coroutine het kan vier verschillende toestanden hebben:
- opgeschort
- haast hebben
- dood
- normaal
Wanneer we het maken, begint het in de staat Stopgezet, wat betekent dat de coroutine niet automatisch wordt uitgevoerd wanneer deze voor de eerste keer wordt gemaakt. De status van een coroutine kan op de volgende manier geraadpleegd worden:
print (coroutine.status (co))Waar we onze coroutine kunnen uitvoeren, hoeven we alleen de functie van te gebruiken vat samen (), wat het intern doet, is de status wijzigen van opgeschort naar actief.
coroutine.resume (co)Als we al onze code samenvoegen en een extra regel toevoegen om de aanvullende status van onze coroutine na het doen op te vragen vat samen we kunnen alle toestanden zien waar het doorheen gaat:
co = coroutine.create (functie () print ("Hello Solvetic") end) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))We gaan naar onze terminal en voeren ons voorbeeld uit, laten we de uitvoer van ons programma bekijken:
lua coroutines1.lua draad: 0x210d880 Opgeschort Hallo Solvetic doodZoals we kunnen zien, is de eerste indruk van de coroutine de waarde van de draad, dan hebben we de staat opgeschort, en dit is prima omdat dit de eerste staat is bij het maken, dan met vat samen We voeren de coroutine uit waarmee het bericht wordt afgedrukt en daarna is de status doodterwijl het zijn missie vervulde.
Coroutines lijken op het eerste gezicht misschien een ingewikkelde manier om functies aan te roepen, maar ze zijn veel complexer dan dat. De kracht van hetzelfde ligt in een groot deel van de functie opbrengst () waarmee een actieve coroutine kan worden onderbroken om de werking later te hervatten, laten we een voorbeeld bekijken van het gebruik van deze functie:
co = coroutine.create (function () for i = 1.10 do print ("samenvattend coroutine", i) coroutine.yield () end end) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co ) coroutine .resume (mede)Wat deze functie zal doen, wordt uitgevoerd tot de eerste opbrengst, en ongeacht of we een cyclus hebben voor, het zal alleen afdrukken volgens zo veel vat samen Laten we voor onze coroutine hebben, om te eindigen, laten we de uitvoer via de terminal bekijken:
lua coroutines 1. lua 1 2 3 4Dit zou de uitgang zijn via de terminal.
Filters
Een van de duidelijkste voorbeelden die coroutines verklaren, is het geval van: klant Y generator van informatie. Stel dat we een functie hebben die continu enkele waarden genereert door het lezen van een bestand en dan hebben we een andere functie die deze leest, laten we een illustratief voorbeeld bekijken van hoe deze functies eruit zouden kunnen zien:
functiegenerator () 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 dit voorbeeld werken zowel de verbruiker als de generator zonder enige vorm van rust en kunnen we ze stoppen wanneer er geen informatie meer te verwerken is, maar het probleem hier is hoe de functies van Versturen() Y ontvangen(), aangezien elk van hen zijn eigen lus heeft en de andere wordt verondersteld een oproepbare service te zijn.
Maar met de coroutines kan dit probleem snel en eenvoudig worden opgelost, met behulp van de dubbele functie hervatten / opbrengst we kunnen onze functies probleemloos laten werken. Wanneer een coroutine de functie aanroept opbrengst, het voert geen nieuwe functie in, maar retourneert een oproep die in behandeling is en die die status alleen kan verlaten met behulp van hervatten.
Zo ook bij het bellen vat samen start ook geen nieuwe functie, maar retourneert een wachtende oproep naar opbrengst, samenvattend is dit proces degene die we nodig hebben om de functies van te synchroniseren Versturen() Y ontvangen(). Als we deze bewerking toepassen, zouden we moeten gebruiken ontvangen() Van toepassing zijn vat samen naar de generator om de nieuwe informatie te genereren en dan Versturen() van toepassing zijn opbrengst Laten we voor de consument eens kijken hoe onze functies eruitzien met de nieuwe wijzigingen:
functie ontvangen () lokale status, waarde = coroutine.resume (generator) retourwaarde einde functie verzenden (x) coroutine.yield (x) end gen = coroutine.create (functie () while true do local x = io.read () stuur (x) einde einde)Maar we kunnen ons programma nog steeds verder verbeteren, en dat is door de filters, dit zijn taken die fungeren als generatoren en consumenten die tegelijkertijd een zeer interessant informatietransformatieproces maken.
EEN filter kan doen vat samen van een generator om nieuwe waarden te krijgen en vervolgens toe te passen opbrengst om gegevens voor de consument te transformeren. Laten we eens kijken hoe we eenvoudig filters kunnen toevoegen aan ons vorige voorbeeld:
gen = generator () fil = filter (gen) consument (fil)Zoals we kunnen zien, was het uiterst eenvoudig, waarbij we naast het optimaliseren van ons programma punten behaalden in leesbaarheid, belangrijk voor toekomstig onderhoud.
Corroutines als iterators
Een van de duidelijkste voorbeelden van de generator/consument is de iterators aanwezig in recursieve cycli, waarbij een iterator informatie genereert die door het lichaam binnen de recursieve cyclus zal worden geconsumeerd, dus het zou niet onredelijk zijn om coroutines te gebruiken om deze iterators te schrijven, zelfs coroutines hebben een speciaal hulpmiddel voor deze taak.
Ter illustratie van het gebruik dat we kunnen maken van: coroutines, we gaan een iterator schrijven om de permutaties van een gegeven array te genereren, dat wil zeggen, elk element van een array op de laatste positie te plaatsen en het om te draaien, en dan recursief alle permutaties van de resterende elementen te genereren, laten we eens kijken hoe onze oorspronkelijke functie zou zijn zonder de coroutines:
functie print_result (var) voor i = 1, #var do io.write (var [i], "") end io.write ("\ n") endWat we nu doen is dit proces volledig veranderen, eerst veranderen we de print_resultaat () door opbrengst, laten we eens kijken naar de verandering:
functie permgen (var1, var2) var2 = var2 of # var1 als var2 <= 1 dan coroutine.yield (var1) elseDit is echter een illustratief voorbeeld om te laten zien hoe iterators werken Lua geeft ons een functie genaamd wrap wat lijkt op creërenHet retourneert echter geen coroutine, het retourneert een functie die, wanneer aangeroepen, een coroutine samenvat. dan te gebruiken wrap we moeten alleen het volgende gebruiken:
functie permutaties (var) return coroutine.wrap (functie () permgen (var) end) endMeestal is deze functie veel gemakkelijker te gebruiken dan: creëren, omdat het ons precies geeft wat we nodig hebben, namelijk om het samen te vatten, maar het is minder flexibel omdat het ons niet in staat stelt om de status van de coroutine te verifiëren die is gemaakt met wrap.
De coroutines in Lua Ze zijn een extreem krachtig hulpmiddel om alles aan te pakken met betrekking tot processen die hand in hand moeten worden uitgevoerd, maar in afwachting van de voltooiing van degene die de informatie verstrekt, kunnen we ook zien hoe ze worden gebruikt om complexe problemen met betrekking tot generator- / consumentenprocessen op te lossen en ook het optimaliseren van de constructie van iterators in onze programma's.