2013-05-16 6 views
7

Ho bisogno di iniziare un'enorme quantità di goroutine e attendere la loro conclusione. Il modo più intuitivo sembra utilizzare un canale di aspettare fino a tutti loro sono finiti:Attendi la cessazione delle n goroutine

package main 

type Object struct { 
    //data 
} 

func (obj *Object) Update(channel chan int) { 
    //update data 
    channel <- 1 
    return 
} 

func main() { 

    channel := make(chan int, n) 
    list := make([]Object, n, m) 
    for { 
     for _, object := range list { 
      go object.Update(channel) 
     } 
     for i := 0; i < n; i++ { 
      <-channel 
     } 
     //now everything has been updated. start again 
    } 
} 

Ma il problema è che la quantità di oggetti e quindi la quantità di goroutines potrebbero cambiare. È possibile modificare la dimensione del buffer di un canale?

C'è forse un modo più elegante per farlo?

+2

Puoi ridistribuire ogni iterazione, ma potresti voler dare un'occhiata a [WaitGroup] (http://golang.org/pkg/sync/#WaitGroup). – tjameson

+0

tjameson, grazie per l'aiuto rapido. Sembra davvero buono. Potresti volerlo rendere una risposta. – lhk

+0

Fatto, con un esempio = D – tjameson

risposta

27

Ho usato WaitGroup come soluzione a questo problema. Tradurre il codice corrente, con alcuni registri per far capire quello che sta succedendo:

package main 

import "sync" 
import "fmt" 
import "time" 

type Object struct { 
    //data 
} 

func (obj *Object) Update(wg *sync.WaitGroup) { 
    //update data 
    time.Sleep(time.Second) 
    fmt.Println("Update done") 
    wg.Done() 
    return 
} 

func main() { 
    var wg sync.WaitGroup 
    list := make([]Object, 5) 
    for { 
     for _, object := range list { 
      wg.Add(1) 
      go object.Update(&wg) 
     } 
     //now everything has been updated. start again 
     wg.Wait() 
     fmt.Println("Group done") 
    } 
} 
+8

Bella risposta! Probabilmente inserirò 'defer wg.Done()' all'inizio di 'Update', anche se nel caso in cui la funzione aumenti e ottenga un ritorno anticipato in futuro. –

+0

O nel caso ci sia un panico o qualcosa del genere. – tjameson

4

Questo compito non è esattamente banale, è abbastanza facile scrivere un buggy. Raccomando di utilizzare una soluzione già pronta nello stdlib - sync.WaitGroup. Citando il link:

Un WaitGroup attende la conclusione di una raccolta di goroutine. Le chiamate di goroutine principali Aggiungi per impostare il numero di goroutine da attendere. Quindi ciascuna delle goroutine viene eseguita e chiama Fatto al termine. Allo stesso tempo, Wait può essere utilizzato per bloccare fino al termine di tutte le goroutine.

+0

E se il numero di goroutine da attendere non è noto in anticipo? – Dfr

+0

@Dfr incrementa il contatore quando avvii ogni goroutine, quindi questa soluzione è ancora la soluzione migliore per quando non conosci il numero di goroutine che inizierai. – Awn

0

@tjameson ha fatto un ottimo lavoro che spiega come utilizzare WaitGroup, come passare un riferimento alla vostra WaitGroup oggetto per la vostra funzione. L'unica modifica che farei al suo esempio è la leva defer quando si è Done. Penso che questo defer ws.Done() dovrebbe essere la prima affermazione nella tua funzione.

Mi piace la semplicità di WaitGroup. Tuttavia, non mi piace che abbiamo bisogno di passare il riferimento alla goroutine perché ciò significherebbe che la logica della concorrenza verrebbe mescolata con la tua logica aziendale.

Così mi è venuta con questa funzione generica per risolvere questo problema per me:

// Parallelize parallelizes the function calls 
func Parallelize(functions ...func()) { 
    var waitGroup sync.WaitGroup 
    waitGroup.Add(len(functions)) 

    defer waitGroup.Wait() 

    for _, function := range functions { 
     go func(copy func()) { 
      defer waitGroup.Done() 
      copy() 
     }(function) 
    } 
} 

Così il vostro esempio può essere risolto in questo modo:

type Object struct { 
    //data 
} 

func (obj *Object) Update() { 
    //update data 
    time.Sleep(time.Second) 
    fmt.Println("Update done") 
    return 
} 

func main() { 
    functions := []func(){} 
    list := make([]Object, 5) 
    for _, object := range list { 
     function := func(obj Object){ object.Update() }(object) 
     functions = append(functions, function) 
    } 

    Parallelize(functions...)   

    fmt.Println("Group done") 
} 

Se si desidera utilizzarlo, lo puoi trovare qui https://github.com/shomali11/util