Coroutine în Lua

A coroutină Este similar cu un thread, este o linie de execuție cu propria stivă, propriile variabile locale și propriul indicator pentru instrucțiuni, dar cu particularitatea că împarte variabile globale și orice alt element cu celelalte coroutine.

Dar trebuie să clarificăm că există diferențe între fire si coroutine, diferența principală este că un program care folosește fire de rulare le rulează simultan, coroutine pe de altă parte, sunt colaborative, în cazul în care un program care folosește coroutine rulează doar una dintre acestea, iar suspendarea acestora se realizează numai dacă este solicitată în mod explicit.

coroutine Sunt extrem de puternici, să vedem ce cuprinde acest concept și cum le putem folosi în programele noastre.

Noțiuni de bază


Toate funcțiile legate de coroutine în Lua se găsesc în tabelul coroutinei, unde funcția crea () ne permite să le creăm, are un argument simplu și este funcția cu codul pe care va rula coroutina, unde returnarea sa este o valoare de tipul firului, care reprezintă noua coroutină. Chiar și argumentul pentru crearea coroutinei este uneori o funcție anonimă ca în exemplul următor:
 co = coroutine.create (function () print ("Hello Solvetic") end)
A coroutină poate avea patru stări diferite:
  • suspendat
  • în grabă
  • mort
  • normal

Când îl creăm, începe în stare întrerupt, ceea ce înseamnă că coroutina nu rulează automat atunci când este creată pentru prima dată. Starea unei coroutine poate fi consultată în felul următor:

 print (coroutine.status (co))
Unde să ne putem rula coroutina trebuie să folosim doar funcția rezumă (), ceea ce face intern este să-și schimbe statutul de la suspendat la funcțional.
 coroutine.resume (co)
Dacă punem tot codul împreună și adăugăm o linie suplimentară pentru a interoga starea suplimentară a coroutinei noastre după ce am făcut-o rezumă putem vedea toate stările prin care trece:
 co = coroutine.create (function () print ("Hello Solvetic") end) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))
Mergem la terminalul nostru și rulăm exemplul nostru, să vedem rezultatul programului nostru:
 lua coroutines1.lua thread: 0x210d880 Suspended Hello Solvetic dead
După cum putem vedea, prima impresie a coroutinei este valoarea firului, atunci avem starea suspendat, și acest lucru este bine, deoarece aceasta este prima stare când creați, apoi cu rezumă Rulăm coroutina cu care imprimă mesajul și după aceea starea lui este mortpe măsură ce și-a îndeplinit misiunea.

Coroutinele la prima vedere pot părea o modalitate complicată de a apela funcții, cu toate acestea sunt mult mai complexe decât atât. Puterea aceluiași se bazează în cea mai mare parte a funcției Randament () care permite suspendarea unei coroutine care rulează pentru a-și relua funcționarea ulterior, să vedem un exemplu de utilizare a acestei funcții:

 co = coroutine.create (function () for i = 1.10 do print ("resumeing coroutine", i) coroutine.yield () end end) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co) coroutine .reîncepe (co)
Ce va face această funcție este să ruleze până la prima Randament, și indiferent dacă avem un ciclu pentru, se va imprima numai în funcție de atâtea rezumă Să avem pentru coroutină, pentru a termina, să vedem ieșirea prin terminal:
 lua coroutines 1. lua 1 2 3 4
Aceasta ar fi ieșirea prin terminal.

Filtre


Unul dintre cele mai clare exemple care explică coroutinele este cazul consumator Da generator De informații. Să presupunem că avem o funcție care generează în mod continuu unele valori din citirea unui fișier și că avem o altă funcție care le citește, să vedem un exemplu ilustrativ despre cum ar putea arăta aceste funcții:
 generator de funcții () în timp ce adevărat face local x = io.read () send (x) end end consumator funcție () în timp ce adevărat face local x = receive () io.write (x, "\ n") end end
În acest exemplu, atât consumatorul, cât și generatorul funcționează fără niciun fel de odihnă și le putem opri atunci când nu mai există informații de procesat, totuși problema aici este cum să sincronizăm funcțiile Trimite() Da a primi(), deoarece fiecare dintre ele are propria buclă, iar cealaltă se presupune că este un serviciu apelabil.

Dar cu coroutinele, această problemă poate fi rezolvată rapid și ușor, utilizând funcția dublă relua / ceda putem face ca funcțiile noastre să funcționeze fără probleme. Când o coroutină apelează funcția Randament, nu intră într-o funcție nouă, ci returnează un apel care este în așteptare și care poate ieși din acea stare doar folosind CV.

În mod similar atunci când sunați rezumă nu pornește nici o funcție nouă, returnează un apel de așteptare la Randament, rezumând acest proces este cel de care avem nevoie pentru a sincroniza funcțiile Trimite() Da a primi(). Aplicând această operațiune ar trebui să o folosim a primi() aplica rezumă către generator pentru a genera noile informații și apoi Trimite() aplica Randament Pentru consumator, să vedem cum arată funcțiile noastre cu noile modificări:

 function receive () status local, value = coroutine.resume (generator) return value end function send (x) coroutine.yield (x) end gen = coroutine.create (function () while true do local x = io.read () send (x) end end)
Dar putem totuși să ne îmbunătățim programul în continuare și este folosind filtre, care sunt sarcini care funcționează ca generatori și consumatori, făcând în același timp un proces foarte interesant de transformare a informațiilor.

A filtru pot face rezumă de la un generator pentru a obține valori noi și apoi a aplica Randament pentru a transforma date pentru consumator. Să vedem cum putem adăuga cu ușurință filtre la exemplul nostru anterior:

 gene = generator () fil = filter (gene) consumer (fil)
După cum putem vedea, a fost extrem de simplu, unde pe lângă optimizarea programului nostru am câștigat puncte de lizibilitate, importante pentru întreținerea viitoare.

Corroutine ca iteratori


Unul dintre cele mai clare exemple de generator / consumator este iteratori prezent în cicluri recursive, în care un iterator generează informații care vor fi consumate de corp în cadrul ciclului recursiv, deci nu ar fi nerezonabil să folosiți coroutine pentru a scrie aceste iteratoare, chiar și coroutinele au un instrument special pentru această sarcină.

Pentru a ilustra utilizarea pe care o putem face coroutine, vom scrie un iterator pentru a genera permutările unui tablou dat, adică așezați fiecare element al unui tablou în ultima poziție și răsturnați-l și apoi generați recursiv toate permutările elementelor rămase, să vedem cum funcția originală ar fi fără a include coroutinele:

 funcția print_result (var) pentru i = 1, #var do io.write (var [i], "") end io.write ("\ n") end
Acum ceea ce facem este să schimbăm complet acest proces, mai întâi îl schimbăm print_result () după randament, să vedem schimbarea:
 function permgen (var1, var2) var2 = var2 or # var1 if var2 <= 1 then coroutine.yield (var1) else
Acesta este un exemplu ilustrativ pentru a demonstra modul în care funcționează iteratorii Lua ne oferă o funcție numită înveliți care este similar cu creaCu toate acestea, nu returnează o coroutină, returnează o funcție care, atunci când este apelată, rezumă o coroutină. Apoi de folosit înveliți ar trebui să folosim doar următoarele:
 function permutations (var) return coroutine.wrap (function () permgen (var) end) end
De obicei, această funcție este mult mai ușor de utilizat decât crea, deoarece ne oferă exact ceea ce avem nevoie, adică să îl rezumăm, totuși este mai puțin flexibil, deoarece nu ne permite să verificăm starea coroutinei create cu înveliți.

Coroutinele din Lua Acestea sunt un instrument extrem de puternic pentru a face față a tot ceea ce ține de procesele care trebuie executate mână în mână, dar așteptând finalizarea celui care furnizează informațiile, am putea vedea și utilizarea lor pentru a rezolva probleme complexe în ceea ce privește procesele generator / consumator. și, de asemenea, optimizarea construcției iteratorilor în programele noastre.

wave wave wave wave wave