Per chiarire la funzione e l'utilità delle fiber in PHP è possibile fare riferimento al concetto di coroutine presente in altri linguaggi come per esempio Python. Partiamo quindi da queste ultime per poi affrontare il discorso delle Fiber proponendo un esempio pratico del loro utilizzo.
Cosa sono le coroutine
Le coroutine permettono di scrivere codice che può essere sospeso e riattivato in corrispondenza di punti specifici. Vengono impiegate ad esempio per gestire operazioni asincrone, come quelle legate all'I/O o all'esecuzione di task concorrenti. Il tutto senza dover fare ricorso a thread o a processi multipli paralleli.
Una delle principali differenze tra le coroutine e i thread riguarda il fatto che con esse i task vengono eseguiti in modo concorrente, senza parallelismo. Tale caratteristica previene comportamenti anomali spesso connessi al multithreading, si pensi ad esempio alla sincronizzazione, quindi si può avere soltanto una coroutine attiva alla volta. Nello stesso modo l'uso delle coroutine ha un impatto relativamente leggero sulle performance in quanto non richiedono la generazione di processi addizionali.
La possibilità di sospendere una coroutine mentre è in fase di esecuzione risulta inoltre molto utile quando si vuole consentire l'attivazione di un'altra coroutine, senza sovrapposizioni, per poi riprendere quella interrotta dal punto in cui è stata sospesa.
Le coroutine in Python
In Python un semplice esempio di utilizzo delle coroutine è rappresentato dal codice seguente:
import asyncio
async def py_coroutine():
print("Avvio")
await asyncio.sleep(1)
print("Conclusione")
asyncio.run(py_coroutine())
Nel linguaggio, le coroutine vengono dichiarate tramite la sintassi basata sulle keyword async
, che introduce e definisce la coroutine, e await
, che ne sospende l'esecuzione mettendola in attesa. La piccola applicazione dell'esempio precedente non fa altro che stampare il termine "Avvio", attendere tre secondi e poi stampare la parola "Conclusione". Da notare come la fase di attesa venga lanciata tramite:
await asyncio.sleep(3)
Nel caso specifico asyncio.sleep(3)
è una funzione asincrona introdotta per simulare un'attesa la cui durata viene specificata dal valore espresso in secondi passato tra le parentesi tonde. Non è stata utilizzata invece time.sleep(3)
, che è una funzione sincrona, in quanto avrebbe bloccato l'esecuzione dello script per la durata indicata e non sarebbe stato possibile eseguire altre coroutine durante la sospensione.
Si tega inoltre conto del fatto che la sola chiamata ad una coroutine non è sufficiente a programmarne l'esecuzione. In Python, infine, le coroutine possono essere attese (awaitable) e ciò permette di introdurre delle coroutine che sono, appunto, attese da altri costrutti dello stesso tipo.
Fiber in PHP
Rese disponibili agli sviluppatori a partire dalla versione 8.1 di PHP, come le coroutine anche le fiber permettono di gestire in modo efficiente il flusso di esecuzione delle applicazioni e offrono il necessario supporto alla programmazione asincrona. Esse consentono di sospendere l'esecuzione di un blocco di codice per poi riprenderla in un determinato momento. L'operazione sospesa, ad esempio l'esecuzione di una funzione, conserva il suo stato per tutti il periodo di sospensione e mantiene quindi gli elementi di contesto, come la chiamata e le variabili passate sotto forma di argomenti.
Tra i vantaggi delle fiber troviamo la possibilità di scrivere codice asincrono senza la necessità di dover ricorrere a callback e con un beneficio in termini di prestazioni e leggibilità. Senza contare il fatto che poter mettere in pausa un processo, per poi riprenderne l'esecuzione, garantisce una migliore gestione delle risorse.
In quali casi pratici potrebbe risultare utile una fiber? Ad esempio quando si effettuano delle chiamate di rete o si accede ad un database. In generale parliamo di procedure di I/O che può essere necessario sospendere, e non "bloccare", conservando lo stato corrente di un'esecuzione.
Sintassi delle fiber
In PHP le fiber diventano utilizzabili tramite la generazione di un'istanza della classe Fiber
. Fatto questo è necessario il passaggio di una funzione che opera come parametro del costruttore. La seconda fase è fondamentale perché la funzione è in pratica il blocco di codice che deve essere eseguito nel contesto della fiber.
Abbiamo visto in precedenza che Python consente di gestire le coroutine tramite async
e await
. In PHP, invece, abbiamo Fiber::suspend()
, il metodo che interrompe l'esecuzione del blocco di codice, e Fiber::resume()
, con cui riprendere l'esecuzione.
Ecco un semplice esempio di fiber in PHP:
function fetchData(string $url, int $delay): string {
echo "Avvio del caricamento dei dati da: $url\n";
Fiber::suspend(); // Sospensione del caricamento
sleep($delay); // Tempo di caricamento
return "Dati caricati da $url";
}
// Definizione di due fiber che caricano dati da delle API
$fiber1 = new Fiber(function() {
return fetchData('https://api1.sorgentedati.io/data', 3);
});
$fiber2 = new Fiber(function() {
return fetchData('https://api2.sorgentedati.io/data', 2);
});
echo "Avvio operazioni...\n";
// Le fiber vengono lanciate per caricare i dati dalle API
$fiber1->start();
$fiber2->start();
echo "Esecuzione di altre operazioni mentre i dati vengono caricati...\n";
// Riavvio delle fiber
$fiber1->resume();
$fiber2->resume();
// Risultati dalle fiber
$data1 = $fiber1->getReturn();
$data2 = $fiber2->getReturn();
echo "Completamento delle operazioni.\n";
echo "Risultati:\n";
echo " - $data1\n";
echo " - $data2\n";
Nel codice, fetchData
simula il caricamento di dati da un'API. Sospende quindi le fiber con Fiber::suspend()
e poi simula un ritardo. Le fiber vengono avviate con start()
che inizia l'esecuzione del codice delle fiber fino al punto in cui viene richiamato fiber::suspend()
. resume()
, invece, riprende le fiber dal punto in cui erano state sospese. In questo modo viene fatta ripartire l'esecuzione delle fiber fino al termine e si possono recuperare i risultati finali tramite getReturn()
.
Svantaggi nell'uso delle fiber
L'utilizzo delle fiber non presenta unicamente dei vantaggi, anche se possiamo affermare con tranquillità che, in questo caso, i benefici superano abbondantemente gli svantaggi. Tra questi ultimi è comunque importante segnalare che le fiber, a differenza dei thread, devono essere controllate "manualmente", specificando quando devono essere sospese e quando riavviate. In secondo luogo è bene ricordare che esse non supportano il parallelismo e possono essere eseguite soltanto una alla volta.
Conclusioni: coroutine e fiber
Le coroutine, in vari linguaggi come per esempio Python, e le fiber, in PHP, sono costrutti simili che permettono di sospendere l'esecuzione di blocchi di codice per poi riprenderla dopo un intervallo di tempo stabilito dallo sviluppatore. Grazie ad esse è possibile sfruttare i vantaggi della programmazione asincrona e della concorrenza. Con benefici anche per quanto riguarda la gestione delle risorse e l'ottimizzazione delle performance.