Lua - Coroutines
introduzione
Le coroutine sono di natura collaborativa, il che consente a due o più metodi di essere eseguiti in modo controllato. Con le coroutine, in un dato momento, viene eseguita solo una coroutine e questa coroutine in esecuzione sospende la sua esecuzione solo quando richiede esplicitamente di essere sospesa.
La definizione di cui sopra può sembrare vaga. Supponiamo di avere due metodi, uno il metodo del programma principale e una coroutine. Quando chiamiamo una coroutine usando la funzione resume, inizia l'esecuzione e quando chiamiamo la funzione yield, sospende l'esecuzione. Anche in questo caso la stessa coroutine può continuare l'esecuzione con un'altra chiamata alla funzione di ripresa da dove era stata sospesa. Questo processo può continuare fino alla fine dell'esecuzione della coroutine.
Funzioni disponibili in Coroutines
La tabella seguente elenca tutte le funzioni disponibili per le coroutine in Lua e il loro utilizzo corrispondente.
Sr.No. | Metodo e scopo |
---|---|
1 | coroutine.create (f) Crea una nuova coroutine con una funzione f e restituisce un oggetto di tipo "thread". |
2 | coroutine.resume (co [, val1, ...]) Riprende la coroutine co e passa i parametri se presenti. Restituisce lo stato dell'operazione e altri valori di ritorno opzionali. |
3 | coroutine.running () Restituisce la coroutine in esecuzione o nil se chiamato nel thread principale. |
4 | coroutine.status (co) Restituisce uno dei valori in esecuzione, normale, sospeso o morto in base allo stato della coroutine. |
5 | coroutine.wrap (f) Come coroutine.create, anche la funzione coroutine.wrap crea una coroutine, ma invece di restituire la coroutine stessa, restituisce una funzione che, quando chiamata, riprende la coroutine. |
6 | coroutine.yield (...) Sospende la coroutine in esecuzione. Il parametro passato a questo metodo funge da valori di ritorno aggiuntivi per la funzione di ripresa. |
Esempio
Vediamo un esempio per comprendere il concetto di coroutine.
co = coroutine.create(function (value1,value2)
local tempvar3 = 10
print("coroutine section 1", value1, value2, tempvar3)
local tempvar1 = coroutine.yield(value1+1,value2+1)
tempvar3 = tempvar3 + value1
print("coroutine section 2",tempvar1 ,tempvar2, tempvar3)
local tempvar1, tempvar2= coroutine.yield(value1+value2, value1-value2)
tempvar3 = tempvar3 + value1
print("coroutine section 3",tempvar1,tempvar2, tempvar3)
return value2, "end"
end)
print("main", coroutine.resume(co, 3, 2))
print("main", coroutine.resume(co, 12,14))
print("main", coroutine.resume(co, 5, 6))
print("main", coroutine.resume(co, 10, 20))
Quando eseguiamo il programma precedente, otterremo il seguente output.
coroutine section 1 3 2 10
main true 4 3
coroutine section 2 12 nil 13
main true 5 1
coroutine section 3 5 6 16
main true 2 end
main false cannot resume dead coroutine
Cosa fa l'esempio precedente?
Come accennato in precedenza, utilizziamo la funzione di ripresa per avviare l'operazione e la funzione di resa per arrestare l'operazione. Inoltre, puoi vedere che ci sono più valori di ritorno ricevuti dalla funzione di ripresa di coroutine.
Per prima cosa, creiamo una coroutine e la assegniamo a un nome di variabile co e la coroutine prende due variabili come parametri.
Quando chiamiamo la prima funzione di ripresa, i valori 3 e 2 vengono mantenuti nelle variabili temporanee valore1 e valore2 fino alla fine della coroutine.
Per farti capire questo, abbiamo usato un tempvar3, che inizialmente è 10 e viene aggiornato a 13 e 16 dalle successive chiamate delle coroutine poiché value1 è mantenuto come 3 durante l'esecuzione della coroutine.
Il primo coroutine.yield restituisce due valori 4 e 3 alla funzione resume, che otteniamo aggiornando i parametri di input 3 e 2 nell'istruzione yield. Riceve anche lo stato vero / falso dell'esecuzione della coroutine.
Un altro aspetto delle coroutine è il modo in cui vengono gestiti i parametri successivi della chiamata di ripresa, nell'esempio sopra; puoi vedere che la variabile coroutine.yield riceve la prossima chiamata params che fornisce un modo potente per eseguire nuove operazioni con la conservazione dei valori param esistenti.
Infine, una volta che tutte le istruzioni nelle coroutine sono state eseguite, le chiamate successive torneranno false e "non può riprendere la coroutine morta" come risposta.
Un altro esempio di coroutine
Esaminiamo una semplice coroutine che restituisce un numero da 1 a 5 con l'aiuto della funzione di resa e della funzione di ripresa. Crea coroutine se non disponibile oppure riprende la coroutine esistente.
function getNumber()
local function getNumberHelper()
co = coroutine.create(function ()
coroutine.yield(1)
coroutine.yield(2)
coroutine.yield(3)
coroutine.yield(4)
coroutine.yield(5)
end)
return co
end
if(numberHelper) then
status, number = coroutine.resume(numberHelper);
if coroutine.status(numberHelper) == "dead" then
numberHelper = getNumberHelper()
status, number = coroutine.resume(numberHelper);
end
return number
else
numberHelper = getNumberHelper()
status, number = coroutine.resume(numberHelper);
return number
end
end
for index = 1, 10 do
print(index, getNumber())
end
Quando eseguiamo il programma precedente, otterremo il seguente output.
1 1
2 2
3 3
4 4
5 5
6 1
7 2
8 3
9 4
10 5
C'è spesso un confronto di coroutine con i thread dei linguaggi multiprogrammazione, ma dobbiamo capire che le coroutine hanno caratteristiche simili di thread ma vengono eseguite solo una alla volta e non vengono mai eseguite contemporaneamente.
Controlliamo la sequenza di esecuzione del programma per soddisfare le esigenze con la fornitura di conservare temporaneamente determinate informazioni. L'utilizzo di variabili globali con le coroutine fornisce ancora più flessibilità alle coroutine.