Хотя sync.waitGroup
(wg) - это канонический путь вперед, он требует, чтобы вы выполнили хотя бы некоторые из ваших wg.Add
вызовов перед вами, wg.Wait
чтобы все завершились. Это может оказаться невозможным для простых вещей, таких как поисковый робот, где вы заранее не знаете количество рекурсивных вызовов и требуется время, чтобы получить данные, которые управляют wg.Add
вызовами. В конце концов, вам нужно загрузить и проанализировать первую страницу, прежде чем вы узнаете размер первого пакета дочерних страниц.
Я написал решение, используя каналы, избегая waitGroup
в своем решении упражнения Tour of Go - поискового робота . Каждый раз, когда запускается одна или несколько программ, вы отправляете номер в children
канал. Каждый раз, когда процедура го приближается к завершению, вы отправляете 1
на done
канал. Когда сумма детей сравняется с суммой готовых, все готово.
Единственное, что меня беспокоит, - это жестко заданный размер results
канала, но это (текущее) ограничение 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
}
}
}
}
Полный исходный код решения