Corroutines i Lua

A coroutine Det liknar en tråd, det är en exekveringslinje med en egen stack, egna lokala variabler och en egen pekare för instruktionerna, men med den särart att den delar globala variabler och andra element med de andra coroutinesna.

Men vi måste klargöra att det finns skillnader mellan trådar och den coroutines, den största skillnaden är att ett program som använder trådar kör dessa samtidigt, coroutines å andra sidan är de samarbetsvilliga, där ett program som använder coroutines bara kör en av dessa, och avstängning av dessa uppnås endast om det uttryckligen begärs.

De coroutines De är extremt kraftfulla, låt oss se vad detta begrepp omfattar och hur vi kan använda dem i våra program.

Grundläggande koncept


Alla funktioner relaterade till coroutines i Lua finns i coroutine -tabellen, där funktionen skapa () tillåter oss att skapa dem, den har ett enkelt argument och är funktionen med koden som coroutinen kommer att köra, där dess retur är ett värde av trådtypen, som representerar den nya coroutinen. Även argumentet för att skapa coroutinen är ibland en anonym funktion som i följande exempel:
 co = coroutine.create (function () print ("Hello Solvetic") slutet)
A coroutine den kan ha fyra olika tillstånd:
  • upphängd
  • har bråttom
  • död
  • vanligt

När vi skapar det börjar det i staten avbruten, vilket innebär att coroutinen inte körs automatiskt när den skapas för första gången. Status för en coroutine kan konsulteras på följande sätt:

 print (coroutine.status (co))
Var vi ska kunna köra vår coroutine behöver vi bara använda funktionen sammanfattar (), vad den gör internt är att ändra dess status från avstängd till körning.
 coroutine.resume (co)
Om vi ​​sätter ihop all vår kod och lägger till en extra rad för att fråga efter ytterligare status för vår coroutine efter att ha gjort sammanfattar vi kan se alla stater genom vilka det passerar:
 co = coroutine.create (function () print ("Hello Solvetic") end) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))
Vi går till vår terminal och kör vårt exempel, låt oss se resultatet av vårt program:
 lua coroutines1.lua tråd: 0x210d880 Suspenderad Hello Solvetic dead
Som vi kan se är det första intrycket av coroutinen trådens värde, då har vi staten upphängd, och det här är bra eftersom detta är det första tillståndet när du skapar, sedan med sammanfattar Vi kör den coroutine som den skriver ut meddelandet med och efter detta är dess status dödsom den fullgjorde sitt uppdrag.

Coroutines vid första anblicken kan verka som ett komplicerat sätt att ringa funktioner, men de är mycket mer komplexa än så. Kraften hos samma vilar i en stor del av funktionen avkastning () som gör det möjligt att avbryta en coroutine som körs för att återuppta sin verksamhet senare, låt oss se ett exempel på användningen av denna funktion:

 co = coroutine.create (funktion () för i = 1.10 skriv ut ("summering coroutine", i) coroutine.yield () end end) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co) coroutine .resume (co)
Vad den här funktionen kommer att göra är att köra tills den första avkastning, och oavsett om vi har en cykel för, det kommer bara att skrivas ut enligt så många sammanfattar Låt oss ha för vår coroutine, för att avsluta låt oss se utgången via terminalen:
 lua coroutines 1. lua 1 2 3 4
Detta skulle vara utgången genom terminalen.

Filter


Ett av de tydligaste exemplen som förklarar coroutines är fallet med konsument Y generator av information. Antag då att vi har en funktion som kontinuerligt genererar några värden från att läsa en fil och sedan har vi en annan funktion som läser dessa, låt oss se ett illustrativt exempel på hur dessa funktioner kan se ut:
 funktionsgenerator () medan true gör lokal x = io.read () skickar (x) slutändfunktionskonsument () medan true gör lokal x = tar emot () io.write (x, "\ n") slutänd
I detta exempel körs både konsumenten och generatorn utan någon form av vila och vi kan stoppa dem när det inte finns mer information att bearbeta, men problemet här är hur man synkroniserar funktionerna i Skicka() Y motta(), eftersom var och en av dem har sin egen slinga, och den andra antas vara en uppringningsbar tjänst.

Men med coroutines kan detta problem lösas snabbt och enkelt med hjälp av dubbelfunktionen CV / avkastning vi kan få våra funktioner att fungera utan problem. När en coroutine kallar funktionen avkastning, det går inte in i en ny funktion utan returnerar ett väntande samtal och som bara kan avsluta det tillståndet genom att använda CV.

På samma sätt när du ringer sammanfattar startar inte heller en ny funktion, det ger ett väntetillägg till avkastning, sammanfattning av denna process är den vi behöver för att synkronisera funktionerna i Skicka() Y motta(). Vi måste använda denna operation motta() Tillämpa sammanfattar till generatorn för att generera den nya informationen och sedan Skicka() tillämpa avkastning För konsumenten, låt oss se hur våra funktioner ser ut med de nya ändringarna:

 funktion ta emot () lokal status, värde = coroutine.resume (generator) returvärde slutfunktion skicka (x) coroutine.yield (x) end gen = coroutine.create (function () medan true do local x = io.read () skicka (x) slutet slut)
Men vi kan fortfarande förbättra vårt program ytterligare, och det är genom att använda filter, som är uppgif.webpter som fungerar som generatorer och konsumenter samtidigt som gör en mycket intressant process för informationstransformation.

A filtrera kan göra sammanfattar från en generator för att få nya värden och sedan ansöka avkastning att transformera data för konsumenten. Låt oss se hur vi enkelt kan lägga till filter i vårt tidigare exempel:

 gen = generator () fil = filter (gen) konsument (fil)
Som vi kan se var det extremt enkelt, där vi förutom att optimera vårt program fick poäng i läsbarhet, viktiga för framtida underhåll.

Corroutines som iteratorer


Ett av de tydligaste exemplen på generator / konsument är iteratorer närvarande i rekursiva cykler, där en iterator genererar information som kommer att konsumeras av kroppen inom den rekursiva cykeln, så det skulle inte vara orimligt att använda coroutines för att skriva dessa iteratorer, även coroutines har ett speciellt verktyg för denna uppgif.webpt.

För att illustrera användningen vi kan göra av coroutines, vi ska skriva en iterator för att generera permutationerna för en given array, det vill säga placera varje element i en array i den sista positionen och vända den och sedan rekursivt generera alla permutationer av de återstående elementen, låt oss se hur våra den ursprungliga funktionen skulle vara utan att inkludera coroutines:

 funktion print_result (var) för i = 1, #var do io.write (var [i], "") slut io.write ("\ n") end
Det vi gör är att helt förändra denna process, först ändrar vi print_result () efter avkastning, låt oss se förändringen:
 funktion permgen (var1, var2) var2 = var2 eller # var1 om var2 <= 1 sedan coroutine.yield (var1) annat
Detta är dock ett illustrativt exempel för att demonstrera hur iteratorer fungerar Lua ger oss en funktion som kallas slå in som liknar skapaDen returnerar dock inte en coroutine, den returnerar en funktion som, när den kallas, sammanfattar en coroutine. Sedan att använda slå in vi ska bara använda följande:
 funktionspermutationer (var) return coroutine.wrap (function () permgen (var) end) end
Vanligtvis är denna funktion mycket lättare att använda än skapa, eftersom det ger oss exakt vad vi behöver, det vill säga för att sammanfatta det, men det är mindre flexibelt eftersom det inte tillåter oss att verifiera statusen för coroutinen skapad med slå in.

Coroutines in Lua De är ett extremt kraftfullt verktyg för att hantera allt som rör processer som måste utföras hand i hand, men i väntan på att den som tillhandahåller informationen kan slutföras kan vi också se deras användning för att lösa komplexa problem när det gäller generator- / konsumentprocesser och även optimera konstruktionen av iteratorer i våra program.

wave wave wave wave wave