Sebbene sync.waitGroup
(wg) sia il modo canonico in avanti, richiede che tu esegua almeno alcune delle tue wg.Add
chiamate prima di essere wg.Wait
completato. Ciò potrebbe non essere fattibile per cose semplici come un web crawler, in cui non si conosce in anticipo il numero di chiamate ricorsive e ci vuole un po 'per recuperare i dati che guidano le wg.Add
chiamate. Dopo tutto, è necessario caricare e analizzare la prima pagina prima di conoscere la dimensione del primo batch di pagine figlie.
Ho scritto una soluzione utilizzando i canali, evitando waitGroup
nella mia soluzione il Tour of Go - esercizio di web crawler . Ogni volta che vengono avviate una o più routine di go, si invia il numero al children
canale. Ogni volta che una routine go sta per essere completata, invii un messaggio 1
al done
canale. Quando la somma dei bambini è uguale alla somma di fatto, abbiamo finito.
La mia unica preoccupazione rimasta è la dimensione hardcoded del results
canale, ma questa è una limitazione (attuale) di Go.
// recursionController is a data structure with three channels to control our Crawl recursion.
// Tried to use sync.waitGroup in a previous version, but I was unhappy with the mandatory sleep.
// The idea is to have three channels, counting the outstanding calls (children), completed calls
// (done) and results (results). Once outstanding calls == completed calls we are done (if you are
// sufficiently careful to signal any new children before closing your current one, as you may be the last one).
//
type recursionController struct {
results chan string
children chan int
done chan int
}
// instead of instantiating one instance, as we did above, use a more idiomatic Go solution
func NewRecursionController() recursionController {
// we buffer results to 1000, so we cannot crawl more pages than that.
return recursionController{make(chan string, 1000), make(chan int), make(chan int)}
}
// recursionController.Add: convenience function to add children to controller (similar to waitGroup)
func (rc recursionController) Add(children int) {
rc.children <- children
}
// recursionController.Done: convenience function to remove a child from controller (similar to waitGroup)
func (rc recursionController) Done() {
rc.done <- 1
}
// recursionController.Wait will wait until all children are done
func (rc recursionController) Wait() {
fmt.Println("Controller waiting...")
var children, done int
for {
select {
case childrenDelta := <-rc.children:
children += childrenDelta
// fmt.Printf("children found %v total %v\n", childrenDelta, children)
case <-rc.done:
done += 1
// fmt.Println("done found", done)
default:
if done > 0 && children == done {
fmt.Printf("Controller exiting, done = %v, children = %v\n", done, children)
close(rc.results)
return
}
}
}
}
Codice sorgente completo per la soluzione