Ora che abbiamo imparato a svolgere un bel po' di operazioni in linguaggio Go, è importante sapere come temporizzarle ovvero come coordinare i loro comportamenti in base al trascorrere del tempo.
Il tempo è un aspetto estremamente importante nell'esecuzione di programmi ed in questa lezione ci concentreremo per lo più su due sue aspetti:
- timeout: quando eseguiamo delle operazioni lunghe - di quel tipo che abbiamo imparato a gestire comodamente con goroutine e
channel - dobbiamo mettere in conto che queste potrebbero non riuscire per alcuni inconvenienti tecnici, seppure sporadicamente. Fissare un timeout significa impostare un limite di tempo entro il quale l'operazione deve essere conclusa. Al suo scadere, il timeout genererà una sorta di evento che verrà raccolto dal nostro codice per fargli corrispondere una determinata azione; - operazioni periodiche: alcuni programmi potrebbero aver bisogno di eseguire la medesima operazione ad intervalli regolari di tempo. Questo può capitare quando vogliamo inviare dei segnali in Rete, ad esempio, o scaricare dati con una certa periodicità. Tutto ciò può riverlarsi utile anche se un'operazione fallisce e vogliamo ripeterla finché non riusciamo a mandarla in porto: in questo caso si parla di politiche di retry.
Vale la pena ricordare che tali meccanismi sono spesso implementati all'interno di librerie e funzionalità. Se ad esempio vogliamo attuare delle connessioni ad un sistema remoto applicando timeout e retry, prima di tutto è importante verificare se essi sono già disponibili negli oggetti che vorremo utilizzare ed integrarle solo nel caso dovessero mancare.
In generale, è utile affrontare questi argomenti ed imparare ad implementarli in proprio, per acquisire nuove conoscenze sul linguaggio e per ragionare un po' su come alcuni meccanismi funzionano dietro le quinte.
Attivare timeout
Nell'esempio che segue, attiviamo una goroutine che produrrà come risultato un numero casuale. Nel farlo, genererà un ritardo voluto che imposteremo noi con la variabile timeout
.
L'esempio è piuttosto scolastico ma utile per esemplificare una situazione molto comune in cui otteniamo dei dati (da file, via Rete o generati in altro modo) subendo un ritardo dovuto sia a
elaborazioni sia a latenza nella comunicazione: in questo nostro esempio creeremo un ritardo fittizio ed i dati che raccolti saranno semplicemente rappresentati dal numero casuale restituito:
package main
import (
"fmt"
"math/rand"
"time"
)
func operazione(durata int, chn chan<- int) {
time.Sleep(time.Duration(durata) * time.Second)
chn <- rand.Intn(100)
}
func main() {
durata := 2
timeout := 4
chn := make(chan int)
go operazione(durata, chn)
select {
case numero_estratto := <-chn:
fmt.Println(numero_estratto)
case <-time.After(time.Duration(timeout) * time.Second):
fmt.Println("Operazione non completata: scattato il TIMEOUT!")
}
}
Mediante le due variabili intere timeout
e durata
impostiamo rispettivamente il limite di tempo concesso all'operazione ed il ritardo fittizio che creeremo. Potremo impostarle a piacimento per far scattare o meno il timout.
Il codice della funzione operazione
è eseguito in modalità asincrona mediante goroutine. Il meccanismo di verifica dell'attivazione del timeout è sviluppato con il costrutto select
(una sorta di switch...case) in cui solo uno dei due case
otterrà un risultato.
Se l'operazione durerà meno del timeout sarà stampato il numero casuale ottenuto, se il timout scatterà prima di ottenere un risultato sarà attivato il secondo caso in cui verrà segnalata la problematica.
Per esperimento, si cambino i valori delle due variabili intere e si verifichino i diversi comportamenti. Ad esempio, nei nostri test, impostando timeout
a 4 e durata
a 2 abbiamo ricevuto in output il numero 81 generato random. Fissando timeout
a 1 e durata
a 5 abbiamo ricevuto in output il messaggio Operazione non completata: scattato il TIMEOUT!
Ripetere operazioni periodicamente
Per eseguire operazioni periodiche dobbiamo attivare un ticker ovvero un meccanismo che scatta ad intervalli di tempo regolari da noi impostati.
Nell'esempio che segue lo faremo scattare ogni secondo e ciò sarà specificato all'inizio della funzione main
quando andremo a dichiararlo con time.NewTicker(1 * time.Second)
. L'attivazione dei ticker con conseguente esecuzione del blocco di codice avverrà con un ciclo for
ed il costrutto range
all'interno di una goroutine dichiarata come funzione anonima:
package main
import (
"fmt"
"time"
)
func main() {
una_volta_al_secondo := time.NewTicker(1 * time.Second)
go func() {
rintocchi := 1
for _ = range una_volta_al_secondo.C {
fmt.Println("Rintocco n. ", rintocchi)
rintocchi++
}
}()
time.Sleep(5 * time.Second)
una_volta_al_secondo.Stop()
}
L'output sarà come il seguente:
Rintocco n. 1
Rintocco n. 2
Rintocco n. 3
Rintocco n. 4
Notiamo che alla fine del main abbiamo eseguito lo stop del ticker dopo un'attesa di cinque secondi. Ciò è stato necessario visto che il ticker lavora in una goroutine quindi su un thread parallelo. Non avessimo fatto così il programma si sarebbe chiuso senza dare il tempo al meccanismo di scattare.
In realtà, non è obbligatorio che il ticker lavori in una goroutine, avremmo potuto fare tutto all'interno del main sul thread principale. Abbiamo però preferito realizzarlo in questo modo per rappresentare una situazione più realistica, infatti spesso un'attività periodica indotta dal ticker svolge operazioni di rete, salvataggi su file, accesso a database e in generale attività più complesse di un print.