Corroutines i Lua

EN coroutine Det ligner en tråd, det er en eksekveringslinje med sin egen stak, sine egne lokale variabler og sin egen markør for instruktionerne, men med det særlige, at den deler globale variabler og ethvert andet element med de andre koroutiner.

Men vi skal præcisere, at der er forskelle mellem tråde og coroutines, den største forskel er, at et program, der bruger tråde, kører disse samtidigt, coroutines på den anden side er de samarbejdsvillige, hvor et program, der bruger coroutines, kun kører en af ​​disse, og suspensionen af ​​disse opnås kun, hvis det udtrykkeligt anmodes om det.

Det coroutines De er ekstremt kraftfulde, lad os se, hvad dette koncept omfatter, og hvordan vi kan bruge dem i vores programmer.

Basale koncepter


Alle funktioner relateret til coroutines i Lua findes i coroutine -tabellen, hvor funktionen skab () giver os mulighed for at oprette dem, den har et simpelt argument og er funktionen med koden, som coroutinen vil køre, hvor dens retur er en værdi af trådtypen, som repræsenterer den nye coroutine. Selv argumentet for at oprette coroutinen er undertiden en anonym funktion som i følgende eksempel:
 co = coroutine.create (function () print ("Hello Solvetic") end)
EN coroutine det kan have fire forskellige tilstande:
  • suspenderet
  • i en fart
  • død
  • normal

Når vi opretter det, starter det i staten ophørt, hvilket betyder, at coroutinen ikke kører automatisk, når den oprettes for første gang. Status for en coroutine kan konsulteres på følgende måde:

 print (coroutine.status (co))
Hvor vi skal kunne køre vores coroutine, skal vi kun bruge funktionen opsummerer (), hvad det gør internt er at ændre sin status fra suspenderet til at køre.
 coroutine.resume (co)
Hvis vi sætter hele vores kode sammen og tilføjer en ekstra linje for at forespørge om yderligere status for vores coroutine efter at have gjort opsummerer vi kan se alle de stater, hvorigennem det passerer:
 co = coroutine.create (funktion () print ("Hello Solvetic") ende) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))
Vi går til vores terminal og kører vores eksempel, lad os se output fra vores program:
 lua coroutines1.lua tråd: 0x210d880 Suspended Hello Solvetic dead
Som vi kan se, er det første indtryk af coroutinen værdien af ​​tråden, så har vi staten suspenderet, og det er fint, da dette er den første tilstand, når du opretter, derefter med opsummerer Vi kører den koroutine, som den udskriver beskeden med, og derefter er dens status dødda den opfyldte sin mission.

Coroutines ved første øjekast kan virke som en kompliceret måde at kalde funktioner på, men de er meget mere komplekse end det. Kraften i det samme hviler i en stor del af funktionen udbytte () som gør det muligt at suspendere en coroutine, der kører for at genoptage driften senere, lad os se et eksempel på brugen af ​​denne funktion:

 co = coroutine.create (funktion () for i = 1.10 print .resume (co)
Hvad denne funktion vil gøre, er at køre indtil den første udbytte, og uanset om vi har en cyklus til, det vil kun udskrive ifølge så mange opsummerer Lad os have for vores coroutine, for at afslutte lad os se output via terminalen:
 lua coroutines 1. lua 1 2 3 4
Dette ville være udgangen gennem terminalen.

Filtre


Et af de tydeligste eksempler, der forklarer coroutines, er tilfældet med forbruger Y generator af information. Antag så, at vi har en funktion, der kontinuerligt genererer nogle værdier ved at læse en fil, og så har vi en anden funktion, der læser disse, lad os se et illustrerende eksempel på, hvordan disse funktioner kan se ud:
 funktionsgenerator () mens sand gør lokal x = io.read () sender (x) slutendefunktionsforbruger () mens sand gør lokal x = modtag () io.write (x, "\ n") slutende
I dette eksempel kører både forbrugeren og generatoren uden nogen form for hvile, og vi kan stoppe dem, når der ikke er flere oplysninger at behandle, men problemet her er, hvordan man synkroniserer funktionerne i Sende() Y modtage(), da hver af dem har sin egen sløjfe, og den anden formodes at være en opkaldelig tjeneste.

Men med coroutines kan dette problem løses hurtigt og nemt ved hjælp af dobbeltfunktionen genoptag / udbytte vi kan få vores funktioner til at fungere uden problemer. Når en coroutine kalder funktionen udbytte, indtaster den ikke en ny funktion, men returnerer et ventende opkald, og som kun kan afslutte denne tilstand ved hjælp af genoptag.

Tilsvarende når man ringer opsummerer starter heller ikke en ny funktion, det returnerer et ventekald til udbytte, opsummerer denne proces den, vi har brug for at synkronisere funktionerne i Sende() Y modtage(). Anvendelse af denne operation skulle vi bruge modtage() ansøge opsummerer til generatoren for at generere de nye oplysninger og derefter Sende() ansøge udbytte For forbrugeren, lad os se, hvordan vores funktioner ser ud med de nye ændringer:

 funktion modtage () lokal status, værdi = coroutine.resume (generator) returværdi slutfunktion send (x) coroutine.yield (x) end gen = coroutine.create (function () mens true do local x = io.read () send (x) ende ende)
Men vi kan stadig forbedre vores program yderligere, og det er ved at bruge filtre, som er opgaver, der fungerer som generatorer og forbrugere på samme tid og laver en meget interessant informationstransformationsproces.

EN filter kan gøre opsummerer fra en generator for at få nye værdier og derefter anvende udbytte at transformere data til forbrugeren. Lad os se, hvordan vi let kan tilføje filtre til vores tidligere eksempel:

 gen = generator () fil = filter (gen) forbruger (fil)
Som vi kan se, var det ekstremt enkelt, hvor vi ud over at optimere vores program fik point i læsbarhed, vigtigt for fremtidig vedligeholdelse.

Korroutiner som iteratorer


Et af de tydeligste eksempler på generatoren / forbrugeren er iteratorer til stede i rekursive cyklusser, hvor en iterator genererer information, der vil blive brugt af kroppen inden for den rekursive cyklus, så det ville ikke være urimeligt at bruge coroutines til at skrive disse iteratorer, selv coroutines har et særligt værktøj til denne opgave.

For at illustrere den brug, vi kan gøre af coroutines, vi skal skrive en iterator for at generere permutationerne af et givet array, det vil sige at placere hvert element i en array i den sidste position og vende det og derefter rekursivt generere alle permutationer af de resterende elementer, lad os se, hvordan vores den originale funktion ville være uden at inkludere coroutines:

 funktion print_result (var) for i = 1, #var do io.write (var [i], "") slut io.write ("\ n") ende
Nu, hvad vi gør, er helt at ændre denne proces, først ændrer vi print_result () efter udbytte, lad os se ændringen:
 funktion permgen (var1, var2) var2 = var2 eller # var1 hvis var2 <= 1 derefter coroutine.yield (var1) andet
Dette er imidlertid et illustrerende eksempel for at demonstrere, hvordan iteratorer fungerer Lua giver os en funktion kaldet wrap som ligner skabDen returnerer imidlertid ikke en coroutine, den returnerer en funktion, der, når den kaldes, opsummerer en coroutine. Derefter at bruge wrap vi bør kun bruge følgende:
 funktion permutationer (var) return coroutine.wrap (funktion () permgen (var) ende) ende
Normalt er denne funktion meget lettere at bruge end skab, da det giver os præcis det, vi har brug for, det vil sige at opsummere det, men det er mindre fleksibelt, da det ikke tillader os at verificere status for coroutinen oprettet med wrap.

Coroutinerne i Lua De er et ekstremt kraftfuldt værktøj til at håndtere alt relateret til processer, der skal udføres hånd i hånd, men venter på færdiggørelsen af ​​den, der giver oplysningerne, vi kunne også se deres brug til at løse komplekse problemer i forbindelse med generator- / forbrugerprocesser og også optimering af konstruktionen af ​​iteratorer i vores programmer.

wave wave wave wave wave