Bien que sync.waitGroup
(wg) est la voie canonique à suivre, mais elle exige que vous fassiez au moins une partie de votre travail d'écriture. wg.Add
les appels avant vous wg.Wait
pour que tous puissent la compléter. Cela peut ne pas être possible pour des choses simples, comme un robot d'exploration du Web, où l'on ne connaît pas le nombre d'appels récursifs à l'avance et où il faut un certain temps pour récupérer les données qui alimentent le processus d'exploration. wg.Add
appels. Après tout, vous devez charger et analyser la première page avant de connaître la taille du premier lot de pages enfant.
J'ai écrit une solution en utilisant des canaux, en évitant waitGroup
dans ma solution le Tour de Go - crawler web exercice. Chaque fois qu'une ou plusieurs go-routines sont lancées, vous envoyez le nombre à l'adresse suivante children
canal. Chaque fois qu'une routine d'exécution est sur le point de s'achever, vous envoyez un message de type 1
a la done
canal. Quand la somme des enfants est égale à la somme des faits, nous avons terminé.
Le seul problème qui subsiste est la taille codée en dur de l'unité de contrôle de l'unité de contrôle. results
mais il s'agit d'une limitation (actuelle) du 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
}
}
}
}
Code source complet de la solution