Embora sync.waitGroup
(wg) seja a maneira canônica de avançar, ele exige que você faça pelo menos algumas de suas wg.Add
chamadas antes wg.Wait
de todas serem concluídas. Isso pode não ser viável para coisas simples como um rastreador da web, onde você não sabe o número de chamadas recursivas de antemão e leva um tempo para recuperar os dados que conduzem owg.Add
chamadas. Afinal, você precisa carregar e analisar a primeira página antes de saber o tamanho do primeiro lote de páginas filhas.
Eu escrevi uma solução usando canais, evitando waitGroup
em minha solução o exercício Tour of Go - web crawler . Cada vez que uma ou mais rotinas de go são iniciadas, você envia o número para o children
canal. Cada vez que uma rotina go está prestes a ser concluída, você envia um 1
para o done
canal. Quando a soma dos filhos for igual à soma de done, estamos prontos.
Minha única preocupação restante é o tamanho do results
canal embutido, mas essa é uma limitação do Go (atual).
// 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
}
}
}
}
Código-fonte completo para a solução