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.