Anstatt sync.WaitGroup
einzubeziehen, können Sie das Ergebnis, das über eine geparste URL gesendet wird, erweitern und die Anzahl neuer URLs einschließen. In Ihrer Hauptschleife würden Sie dann die Ergebnisse lesen, solange es etwas zu sammeln gibt.
In Ihrem Fall würde die Anzahl der gefundenen URLs Anzahl der ausgeführten Routinen sein, aber das muss nicht unbedingt sein. Ich würde persönlich mehr oder weniger feste Anzahl von Holroutinen erzeugen, also öffnest du nicht zu viele HTTP-Anfragen (oder zumindest hast du die Kontrolle darüber). Ihre Hauptschleife würde sich dann nicht ändern, da es nicht wichtig ist, wie das Holen ausgeführt wird. Die wichtige Tatsache hier ist, dass Sie entweder ein Ergebnis oder einen Fehler für jede URL senden müssen - ich habe den Code hier geändert, so dass es keine neuen Routinen erzeugt, wenn die Tiefe bereits 1 ist.
Ein Nebeneffekt von Diese Lösung besteht darin, dass Sie den Fortschritt in Ihrer Hauptschleife einfach ausdrucken können. Hier
ist das Beispiel, das auf Spielplatz:
http://play.golang.org/p/BRlUc6bojf
package main
import (
"fmt"
)
type Fetcher interface {
// Fetch returns the body of URL and
// a slice of URLs found on that page.
Fetch(url string) (body string, urls []string, err error)
}
type Res struct {
url string
body string
found int // Number of new urls found
}
// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher, ch chan Res, errs chan error, visited map[string]bool) {
body, urls, err := fetcher.Fetch(url)
visited[url] = true
if err != nil {
errs <- err
return
}
newUrls := 0
if depth > 1 {
for _, u := range urls {
if !visited[u] {
newUrls++
go Crawl(u, depth-1, fetcher, ch, errs, visited)
}
}
}
// Send the result along with number of urls to be fetched
ch <- Res{url, body, newUrls}
return
}
func main() {
ch := make(chan Res)
errs := make(chan error)
visited := map[string]bool{}
go Crawl("http://golang.org/", 4, fetcher, ch, errs, visited)
tocollect := 1
for n := 0; n < tocollect; n++ {
select {
case s := <-ch:
fmt.Printf("found: %s %q\n", s.url, s.body)
tocollect += s.found
case e := <-errs:
fmt.Println(e)
}
}
}
// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
body string
urls []string
}
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
if res, ok := f[url]; ok {
return res.body, res.urls, nil
}
return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
"http://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"http://golang.org/pkg/",
"http://golang.org/cmd/",
},
},
"http://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"http://golang.org/",
"http://golang.org/cmd/",
"http://golang.org/pkg/fmt/",
"http://golang.org/pkg/os/",
},
},
"http://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
"http://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"http://golang.org/",
"http://golang.org/pkg/",
},
},
}
Und ja, @jimt Rat folgen und sicheren Zugriff auf die Karte Thread machen.
Ich habe dies als die Antwort markiert, weil, während ich denke, die Lösung @jimt zur Verfügung gestellt ist elegant, und lehrt eine Menge nützlicher Dinge, das ist getan, ohne auf Werkzeuge über den Rahmen der vorherigen Tutorials verlassen. Ich habe jedoch nach der idiomatischen "richtigen Antwort" gefragt, und da ich nicht erfahren genug bin, weiß ich nicht, welches das ist, was ich dafür markieren würde. (versucht, beide Antworten zu akzeptieren) – SamMorrowDrums