2015-09-29 26 views
7

Qual è un modo idiomatico per assegnare un timeout a WaitGroup.Wait()?Timeout per WaitGroup.Wait()

Il motivo per cui voglio farlo, è quello di salvaguardare il mio 'scheduler' da potenzialmente in attesa di un 'operaio' errante per sempre. Questo porta ad alcune domande filosofiche (ad esempio, come può il sistema continuare in modo affidabile una volta che ha lavoratori in errore?), Ma penso che sia fuori portata per questa domanda.

Ho una risposta che fornirò. Ora che l'ho scritto, non sembra così male, ma si sente ancora più contorto di quanto dovrebbe. Mi piacerebbe sapere se c'è qualcosa che è più semplice, più idiomatico, o anche un approccio alternativo che non usa WaitGroups.

Ta.

risposta

15

Principalmente la soluzione che hai pubblicato below è il massimo che può ottenere. Un paio di suggerimenti per migliorarlo:

  • In alternativa si può chiudere il canale per segnalare il completamento invece di inviare un valore su di esso, lo svolgimento della ricezione su un canale chiuso can always proceed immediately.
  • Ed è meglio usare l'istruzione defer per segnalare il completamento, viene eseguita anche se una funzione si interrompe bruscamente.
  • Anche se è necessario attendere un solo "processo", è possibile omettere completamente lo WaitGroup e inviare semplicemente un valore o chiudere il canale al termine del lavoro (lo stesso canale utilizzato nell'istruzione select).
  • Specificare una durata di 1 secondo è semplice: timeout := time.Second. Ad esempio, specificare 2 secondi è: timeout := 2 * time.Second. Non è necessaria la conversione, time.Second è già di tipo time.Duration, moltiplicandolo con una costante non tipizzata come 2 anche un valore di tipo time.Duration.

Vorrei anche creare una funzione di supporto/utilità che avvolge questa funzionalità. Si noti che WaitGroup deve essere passato come puntatore altrimenti la copia non riceverà la "notifica" delle chiamate WaitGroup.Done(). Qualcosa di simile:

// waitTimeout waits for the waitgroup for the specified max timeout. 
// Returns true if waiting timed out. 
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { 
    c := make(chan struct{}) 
    go func() { 
     defer close(c) 
     wg.Wait() 
    }() 
    select { 
    case <-c: 
     return false // completed normally 
    case <-time.After(timeout): 
     return true // timed out 
    } 
} 

Usandolo:

if waitTimeout(&wg, time.Second) { 
    fmt.Println("Timed out waiting for wait group") 
} else { 
    fmt.Println("Wait group finished") 
} 

Prova sul Go Playground.

+0

grazie, risposta molto approfondita. Alla fine ho smesso di usare un WaitGroup del tutto, e invece ho usato un singolo canale. Il mio codice è diventato più semplice, più chiaro e "meno problematico". Il mio codice poteva contare il numero di iterazioni, quindi potevo usare l'istruzione select un numero fisso di volte, quindi usare un singolo NewTimer per il timeout. Per completezza, aggiungerò un'altra risposta a tempo debito. – laher

+1

Quindi, un po 'di FYI visto che questo è appena spuntato in # go-nuts: Questo * non * funzionerà se le chiamate di 'wg.Add' sono * all'interno di una goroutine *. Basta commentare nel caso qualcuno usi questo codice in modo simile al numero # go-nuts appena risolto! –

+1

se 'wg.Wait()' non ritorna mai, ha fatto trapelare una goroutine? – coanor

4

ho fatto in questo modo: http://play.golang.org/p/eWv0fRlLEC

go func() { 
    wg.Wait() 
    c <- struct{}{} 
}() 
timeout := time.Duration(1) * time.Second 
fmt.Printf("Wait for waitgroup (up to %s)\n", timeout) 
select { 
case <-c: 
    fmt.Printf("Wait group finished\n") 
case <-time.After(timeout): 
    fmt.Printf("Timed out waiting for wait group\n") 
} 
fmt.Printf("Free at last\n") 

Funziona bene, ma è il modo migliore per farlo?

0

Ho scritto una libreria che incapsula la logica di concorrenza https://github.com/shomali11/parallelizer che è possibile anche passare un timeout.

Ecco un esempio senza un timeout:

func main() { 
    group := parallelizer.DefaultGroup() 

    group.Add(func() { 
     for char := 'a'; char < 'a'+3; char++ { 
      fmt.Printf("%c ", char) 
     } 
    }) 

    group.Add(func() { 
     for number := 1; number < 4; number++ { 
      fmt.Printf("%d ", number) 
     } 
    }) 

    err := group.Run() 

    fmt.Println() 
    fmt.Println("Done") 
    fmt.Printf("Error: %v", err) 
} 

uscita:

a 1 b 2 c 3 
Done 
Error: <nil> 

Ecco un esempio con un timeout:

func main() { 
    options := &parallelizer.Options{Timeout: time.Second} 
    group := parallelizer.NewGroup(options) 

    group.Add(func() { 
     time.Sleep(time.Minute) 

     for char := 'a'; char < 'a'+3; char++ { 
      fmt.Printf("%c ", char) 
     } 
    }) 

    group.Add(func() { 
     time.Sleep(time.Minute) 

     for number := 1; number < 4; number++ { 
      fmt.Printf("%d ", number) 
     } 
    }) 

    err := group.Run() 

    fmt.Println() 
    fmt.Println("Done") 
    fmt.Printf("Error: %v", err) 
} 

uscita:

Done 
Error: timeout 
0

Questa non è una risposta reale a questa domanda, ma era la soluzione (molto più semplice) al mio piccolo problema quando avevo questa domanda.

I miei "worker" stavano facendo richieste http.Get() quindi ho appena impostato il timeout sul client http.

urls := []string{"http://1.jpg", "http://2.jpg"} 
wg := &sync.WaitGroup{} 
for _, url := range urls { 
    wg.Add(1) 
    go func(url string) { 
     client := http.Client{ 
      Timeout: time.Duration(3 * time.Second), // only want very fast responses 
     } 
     resp, err := client.Get(url) 
     //... check for errors 
     //... do something with the image when there are no errors 
     //... 

     wg.Done() 
    }(url) 

} 
wg.Wait()