Timeouts

Per evitare lo spinning all'infinito o il blocco nel main si può usare un timeout. Questo è prodotto dalla funzione After del package time che prende come argomento un intervallo di tempo e pone un valore su un suo channel predefinito allo scadere dell'intervallo.

Il programma seguente illustra i timeout con due esempi.

Nel primo esempio arriva prima il timeout della goroutine, nel secondo caso prima la goroutine del timeout.

Esempio

(280timeouts.go):

package main

import "time"
import "fmt"

func main() {
	// Goroutine che comunica su channel dopo 2s
	c1 := make(chan string, 1)
	go func() {
		time.Sleep(time.Second * 2)
		c1 <- "result 1"
	}()

	select {
	case res := <-c1:
		fmt.Println(res)
	// timeout dopo un secondo
	case <-time.After(time.Second * 1):
		fmt.Println("timeout 1")
	}

	// Goroutine che comunica su channel dopo 2s
	c2 := make(chan string, 1)
	go func() {
		time.Sleep(time.Second * 2)
		c2 <- "result 2"
	}()

	select {
	case res := <-c2:
		fmt.Println(res)
	// timeout dopo 3 secondi
	case <-time.After(time.Second * 3):
		fmt.Println("timeout 2")
	}
	fmt.Println("Terminated")
}

Break ad etichetta

Supponiamo di sostituire l'ultimo select del secondo esempio con:

  for {
    select {
      case res := <-c2:
        fmt.Println(res)
      // timeout dopo 3 secondi
      case <-time.After(time.Second * 3):
        fmt.Println("timeout 2")
    }
  }

Il programma segnala correttamente result 2 ma poi torna in loop infinito a dire timeout 2 ogni 3 secondi. Occorre uscire dal for dopo l'uno o l'altro case, ma non si può usare return poichè uscirebbe dall'intero programma e non stamperebbe Terminated.

Se si usa il break come nel frammento seguente:

  for {
    select {
      case res := <-c2:
        fmt.Println(res)
        break
      // timeout dopo 3 secondi
      case <-time.After(time.Second * 3):
        fmt.Println("timeout 2")
        break
    }
  }
  fmt.Println("Terminated")

Il risultato non cambia perchè break esce solo dal costrutto corrente, che è il select, non il for.

La soluzione è un break a label:

loop:
  for {
    select {
      case res := <-c2:
        fmt.Println(res)
        break loop
      // timeout dopo 3 secondi
      case <-time.After(time.Second * 3):
        fmt.Println("timeout 2")
        break loop
    }
  }
  fmt.Println("Terminated")

loop: è un'etichetta (una stringa seguita da due punti), che marca il costrutto seguente.

break loop causa l'uscita dal costrutto marcato con l'etichetta loop.

Notare che non si tratta di un goto come in linguaggio C, ma di qualcosa di più complesso.

Un costrutto molto simile esiste anche in Java.