Corroutine a Lua

UN coroutine E' simile ad un thread, è una riga di esecuzione con il suo stack, le sue variabili locali e il suo puntatore per le istruzioni, ma con la particolarità che condivide le variabili globali e qualsiasi altro elemento con le altre coroutine.

Ma dobbiamo chiarire che ci sono differenze tra i discussioni e il coroutine, la differenza principale è che un programma che utilizza i thread li esegue contemporaneamente, il coroutine sono invece collaborative, dove un programma che utilizza coroutine ne esegue solo una, e la sospensione di queste si ottiene solo se esplicitamente richiesta.

Il coroutine Sono estremamente potenti, vediamo cosa comprende questo concetto e come possiamo usarli nei nostri programmi.

Concetti basilari


Tutte le funzioni relative alle coroutine in Lua si trovano nella tabella coroutine, dove la funzione creare () ci permette di crearli, ha un argomento semplice ed è la funzione con il codice che eseguirà la coroutine, dove il suo ritorno è un valore del tipo thread, che rappresenta la nuova coroutine. Anche l'argomento per creare la coroutine a volte è una funzione anonima come nell'esempio seguente:
 co = coroutine.create (function () print ("Hello Solvetic") end)
UN coroutine può avere quattro diversi stati:
  • sospeso
  • di fretta
  • morto
  • normale

Quando lo creiamo, inizia nello stato interrotto, il che significa che la coroutine non viene eseguita automaticamente quando viene creata per la prima volta. Lo stato di una coroutine può essere consultato nel seguente modo:

 print (coroutine.status (co))
Dove per poter eseguire la nostra coroutine dobbiamo solo usare la funzione di riassume(), ciò che fa internamente è cambiare il suo stato da sospeso a in esecuzione.
 coroutine.resume (co)
Se mettiamo insieme tutto il nostro codice e aggiungiamo una riga aggiuntiva per interrogare lo stato aggiuntivo della nostra coroutine dopo averlo fatto riassume possiamo vedere tutti gli stati attraverso i quali passa:
 co = coroutine.create (function () print ("Hello Solvetic") end) print (co) print (coroutine.status (co)) coroutine.resume (co) print (coroutine.status (co))
Andiamo al nostro terminale ed eseguiamo il nostro esempio, vediamo l'output del nostro programma:
 lua coroutines1.lua thread: 0x210d880 Sospeso Hello Solvetic dead
Come possiamo vedere la prima impressione della coroutine è il valore del thread, quindi abbiamo lo stato sospeso, e questo va bene poiché questo è il primo stato durante la creazione, quindi con riassume Eseguiamo la coroutine con cui stampa il messaggio e dopo questo il suo stato è mortocome ha compiuto la sua missione.

Le coroutine a prima vista possono sembrare un modo complicato per chiamare le funzioni, tuttavia sono molto più complesse di così. La potenza dello stesso risiede in gran parte della funzione prodotto () che permette di sospendere una coroutine in esecuzione per riprenderne il funzionamento in un secondo momento, vediamo un esempio di utilizzo di questa funzione:

 co = coroutine.create (function () for i = 1.10 do print ("riassumendo coroutine", i) coroutine.yield () end end) coroutine.resume (co) coroutine.resume (co) coroutine.resume (co ) coroutine .riprendi (co)
Ciò che farà questa funzione è eseguire fino al primo prodotto, e indipendentemente dal fatto che abbiamo un ciclo per, stamperà solo in base a tanti riassume Facciamo per la nostra coroutine, per finire vediamo l'output attraverso il terminale:
 lua coroutine 1. lua 1 2 3 4
Questa sarebbe l'uscita attraverso il terminale.

Filtri


Uno degli esempi più chiari che spiegano le coroutine è il caso di consumatoreGeneratore di informazione. Supponiamo quindi di avere una funzione che genera continuamente dei valori dalla lettura di un file e quindi di avere un'altra funzione che li legge, vediamo un esempio illustrativo di come potrebbero essere queste funzioni:
 generatore di funzioni () while true do local x = io.read () send (x) end end function consumer () while true do local x = receiver () io.write (x, "\ n") end end
In questo esempio sia il consumatore che il generatore funzionano senza alcun tipo di riposo e possiamo fermarli quando non ci sono più informazioni da elaborare, tuttavia il problema qui è come sincronizzare le funzioni di Spedire() ricevere(), poiché ognuno di essi ha il proprio ciclo e si presume che l'altro sia un servizio chiamabile.

Ma con le coroutine questo problema può essere risolto in modo semplice e veloce, utilizzando la doppia funzione riprendere / cedere possiamo far funzionare le nostre funzioni senza problemi. Quando una coroutine chiama la funzione prodotto, non immette una nuova funzione ma restituisce una chiamata in sospeso e che può uscire da quello stato solo utilizzando resume.

Allo stesso modo quando si chiama riassume non avvia nemmeno una nuova funzione, restituisce una chiamata di attesa a prodotto, riassumendo questo processo è quello di cui abbiamo bisogno per sincronizzare le funzioni di Spedire()ricevere(). Applicando questa operazione dovremmo usare ricevere() Applicare riassume al generatore per generare le nuove informazioni e poi Spedire() applicare prodotto Per il consumatore, vediamo come appaiono le nostre funzioni con le nuove modifiche:

 funzione riceve () stato locale, valore = coroutine.resume (generatore) valore restituito fine funzione invia (x) coroutine.yield (x) fine gen = coroutine.create (funzione () mentre true do local x = io.read () invia (x) fine fine)
Ma possiamo ancora migliorare ulteriormente il nostro programma, ed è usando il filtri, che sono compiti che funzionano come generatori e consumatori allo stesso tempo, realizzando un processo di trasformazione delle informazioni molto interessante.

UN filtro può fare riassume da un generatore per ottenere nuovi valori e poi applicare prodotto trasformare i dati per il consumatore. Vediamo come possiamo aggiungere facilmente filtri al nostro esempio precedente:

 gene = generatore () fil = filtro (gene) consumatore (fil)
Come possiamo vedere, è stato estremamente semplice, dove oltre a ottimizzare il nostro programma abbiamo guadagnato punti nella leggibilità, importanti per la manutenzione futura.

Corroutine come iteratori


Uno degli esempi più chiari del generatore/consumatore è il iteratori presente nei cicli ricorsivi, dove un iteratore genera informazioni che verranno consumate dal corpo all'interno del ciclo ricorsivo, quindi non sarebbe irragionevole usare le coroutine per scrivere questi iteratori, anche le coroutine hanno uno strumento speciale per questo compito.

Per illustrare l'uso che possiamo fare di coroutine, andremo a scrivere un iteratore per generare le permutazioni di un dato array, cioè, posizionare ogni elemento di un array nell'ultima posizione e capovolgerlo, e quindi generare ricorsivamente tutte le permutazioni degli elementi rimanenti, vediamo come il nostro la funzione originale sarebbe senza includere le coroutine:

 funzione print_result (var) for i = 1, #var do io.write (var [i], "") end io.write ("\ n") end
Ora quello che facciamo è cambiare completamente questo processo, prima cambiamo il stampa_risultato() per rendimento, vediamo la modifica:
 funzione permgen (var1, var2) var2 = var2 o # var1 se var2 <= 1 allora coroutine.yield (var1) altrimenti
Tuttavia, questo è un esempio illustrativo per dimostrare come funzionano gli iteratori Lua ci fornisce una funzione chiamata avvolgere che è simile a creareTuttavia, non restituisce una coroutine, restituisce una funzione che, quando chiamata, riassume una coroutine. Allora per usare avvolgere dovremmo usare solo quanto segue:
 permutazioni di funzioni (var) return coroutine.wrap (function () permgen (var) end) end
Di solito questa funzione è molto più facile da usare di creare, poiché ci dà esattamente ciò di cui abbiamo bisogno, ovvero riassumerlo, tuttavia è meno flessibile poiché non ci consente di verificare lo stato della coroutine creata con avvolgere.

Le coroutine in Lua Sono uno strumento estremamente potente per affrontare tutto ciò che riguarda i processi che devono essere eseguiti di pari passo ma in attesa del completamento di chi fornisce le informazioni, potremmo anche vedere il loro utilizzo per risolvere problemi complessi in merito a processi generatori/consumatori e anche ottimizzando la costruzione di iteratori nei nostri programmi.

Aiuterete lo sviluppo del sito, condividere la pagina con i tuoi amici

wave wave wave wave wave