Corroutines i Lua

EN coroutine Det ligner en tråd, det er en utførelseslinje med sin egen stabel, sine egne lokale variabler og sin egen peker for instruksjonene, men med særegenheten at den deler globale variabler og ethvert annet element med de andre koroutinene.

Men vi må presisere at det er forskjeller mellom tråder og koroutiner, hovedforskjellen er at et program som bruker tråder kjører disse samtidig, koroutiner på den annen side er de samarbeidende, hvor et program som bruker coroutines bare kjører en av disse, og suspensjonen av disse oppnås bare hvis det eksplisitt blir forespurt.

De koroutiner De er ekstremt kraftige, la oss se hva dette konseptet omfatter og hvordan vi kan bruke dem i programmene våre.

Enkle konsepter


Alle funksjoner relatert til coroutines i Lua finnes i coroutine -tabellen, der funksjonen skape () lar oss lage dem, den har et enkelt argument og er funksjonen med koden som coroutinen vil kjøre, der returen er en verdi av trådtypen, som representerer den nye coroutinen. Selv argumentet for å lage coroutinen er noen ganger en anonym funksjon som i følgende eksempel:
 co = coroutine.create (function () print ("Hello Solvetic") slutten)
EN coroutine den kan ha fire forskjellige tilstander:
  • suspendert
  • har det travelt
  • død
  • vanlig

Når vi lager det, starter det i staten avviklet, noe som betyr at coroutinen ikke kjøres automatisk når den opprettes for første gang. Statusen til en coroutine kan konsulteres på følgende måte:

 print (coroutine.status (co))
Hvor vi skal kunne kjøre coroutinen vår, trenger vi bare å bruke funksjonen til oppsummerer (), det den gjør internt, er å endre statusen fra suspendert til å kjøre.
 coroutine.resume (co)
Hvis vi setter sammen all koden vår og legger til en ekstra linje for å spørre tilleggsstatusen til coroutinen vår etter å ha gjort oppsummerer vi kan se alle delstatene den passerer gjennom:
 co = coroutine.create (function () print ("Hello Solvetic") end) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))
Vi går til terminalen vår og kjører vårt eksempel, la oss se utdataene fra programmet vårt:
 lua coroutines1.lua tråd: 0x210d880 Suspended Hello Solvetic dead
Som vi kan se er førsteinntrykket av coroutinen verdien av tråden, så har vi staten suspendert, og dette er greit siden dette er den første tilstanden når du oppretter, deretter med oppsummerer Vi kjører coroutinen som den skriver ut meldingen med, og etter dette er statusen dødsom den oppfylte sitt oppdrag.

Coroutines ved første øyekast kan virke som en komplisert måte å kalle funksjoner på, men de er mye mer komplekse enn det. Kraften til det samme hviler i en stor del av funksjonen utbytte () som gjør det mulig å suspendere en coroutine som kjører for å gjenoppta driften senere, la oss se et eksempel på bruk av denne funksjonen:

 co = coroutine.create (funksjon () for i = 1.10 gjør print ("summering coroutine", i) coroutine.yield () end end) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co) coroutine .resume (co)
Hva denne funksjonen vil gjøre er å kjøre til den første utbytte, og uansett om vi har en syklus til, det vil bare skrive ut i henhold til så mange oppsummerer La oss ha for vår coroutine, for å fullføre, la oss se utgangen gjennom terminalen:
 lua coroutines 1. lua 1 2 3 4
Dette ville være avkjørselen gjennom terminalen.

Filtre


Et av de tydeligste eksemplene som forklarer coroutines er tilfellet med forbruker Y generator av informasjon. Anta at vi har en funksjon som kontinuerlig genererer noen verdier fra å lese en fil, og så har vi en annen funksjon som leser disse, la oss se et illustrerende eksempel på hvordan disse funksjonene kan se ut:
 funksjonsgenerator () mens true gjør lokal x = io.read () send (x) end end -funksjon forbruker () mens true gjør lokal x = mottar () io.write (x, "\ n") end end
I dette eksemplet kjører både forbrukeren og generatoren uten noen form for hvile, og vi kan stoppe dem når det ikke er mer informasjon å behandle, men problemet her er hvordan du synkroniserer funksjonene til Sende() Y motta(), siden hver av dem har sin egen sløyfe, og den andre antas å være en oppringbar tjeneste.

Men med coroutines kan dette problemet løses raskt og enkelt ved hjelp av dobbelfunksjonen gjenoppta / gi vi kan få våre funksjoner til å fungere uten problemer. Når en coroutine kaller funksjonen utbytte, går den ikke inn i en ny funksjon, men returnerer et ventende anrop og som bare kan avslutte denne tilstanden ved å bruke gjenoppta.

På samme måte når du ringer oppsummerer starter heller ikke en ny funksjon, den returnerer et venteanrop til utbytte, oppsummering av denne prosessen er den vi trenger for å synkronisere funksjonene til Sende() Y motta(). Vi må bruke denne operasjonen motta() Søke om oppsummerer til generatoren for å generere den nye informasjonen og deretter Sende() søke om utbytte For forbrukeren, la oss se hvordan funksjonene våre ser ut med de nye endringene:

 funksjon motta () lokal status, verdi = coroutine.resume (generator) returverdi sluttfunksjon send (x) coroutine.yield (x) end gen = coroutine.create (function () mens true do local x = io.read () send (x) end end)
Men vi kan fortsatt forbedre programmet vårt ytterligere, og det er ved å bruke filtre, som er oppgaver som fungerer som generatorer og forbrukere samtidig som gjør en veldig interessant informasjonstransformasjonsprosess.

EN filter kan gjøre oppsummerer fra en generator for å få nye verdier og deretter søke utbytte å transformere data for forbrukeren. La oss se hvordan vi enkelt kan legge til filtre i vårt tidligere eksempel:

 gen = generator () fil = filter (gen) forbruker (fil)
Som vi kan se, var det ekstremt enkelt, der vi i tillegg til å optimalisere programmet fikk poeng i lesbarhet, viktig for fremtidig vedlikehold.

Korroutiner som iteratorer


Et av de tydeligste eksemplene på generatoren / forbrukeren er iteratorer tilstede i rekursive sykluser, hvor en iterator genererer informasjon som vil bli konsumert av kroppen i den rekursive syklusen, så det ville ikke være urimelig å bruke coroutines til å skrive disse iteratorene, selv koroutiner har et spesielt verktøy for denne oppgaven.

For å illustrere bruken vi kan gjøre av koroutiner, vi skal skrive en iterator for å generere permutasjonene til en gitt matrise, det vil si plassere hvert element i en matrise i den siste posisjonen og snu den, og deretter rekursivt generere alle permutasjonene til de gjenværende elementene, la oss se hvordan våre den opprinnelige funksjonen ville være uten å inkludere coroutines:

 funksjon print_result (var) for i = 1, #var do io.write (var [i], "") ende io.write ("\ n") ende
Det vi gjør er å endre denne prosessen fullstendig, først endrer vi print_result () etter avkastning, la oss se endringen:
 funksjon permgen (var1, var2) var2 = var2 eller # var1 hvis var2 <= 1 så coroutine.yield (var1) annet
Dette er imidlertid et illustrerende eksempel for å demonstrere hvordan iteratorer fungerer Lua gir oss en funksjon som kalles pakke inn som ligner på skapeImidlertid returnerer den ikke en coroutine, den returnerer en funksjon som, når den kalles, oppsummerer en coroutine. Deretter å bruke pakke inn vi bør bare bruke følgende:
 funksjonspermutasjoner (var) retur coroutine.wrap (funksjon () permgen (var) ende) ende
Vanligvis er denne funksjonen mye enklere å bruke enn skape, siden det gir oss akkurat det vi trenger, som er å oppsummere det, men det er mindre fleksibelt siden det ikke tillater oss å verifisere statusen til coroutinen som er opprettet med pakke inn.

Koroutinene i Lua De er et ekstremt kraftig verktøy for å håndtere alt relatert til prosesser som må utføres hånd i hånd, men i påvente av fullføringen av den som gir informasjonen, kan vi også se hvordan de brukes til å løse komplekse problemer i forbindelse med generator- / forbrukerprosesser og også optimalisere konstruksjonen av iteratorer i programmene våre.

wave wave wave wave wave