26

私はゴーツアーを行っていますが、並行性を除いて、言語をかなりよく理解しているように感じます。

スライド10は、読者にWebクローラーを並列化するように求める演習です(繰り返しをカバーしないようにしますが、まだそこに到達していません)。

これが私がこれまでに持っているものです:

func Crawl(url string, depth int, fetcher Fetcher, ch chan string) {
    if depth <= 0 {
        return
    }

    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        ch <- fmt.Sprintln(err)
        return
    }

    ch <- fmt.Sprintf("found: %s %q\n", url, body)
    for _, u := range urls {
        go Crawl(u, depth-1, fetcher, ch)
    }
}

func main() {
    ch := make(chan string, 100)
    go Crawl("http://golang.org/", 4, fetcher, ch)

    for i := range ch {
        fmt.Println(i)
    }
}

私の質問は、どこにclose(ch)電話をかけるかです。

defer close(ch)メソッドのどこかに置くとCrawl、プログラムは、スポーンされたゴルーチンの1つから閉じたチャネルに書き込むことになります。これは、の呼び出しがCrawl、スポーンされたゴルーチンよりも先に返されるためです。

私が示すように、への呼び出しを省略するとclose(ch)、すべてのゴルーチンが戻ったときにチャネルが閉じられることはないため、プログラムはチャネルの範囲を指定するmain関数でデッドロックします。

4

21 に答える 21

27

Effective Goの並列化セクションを見ると、ソリューションのアイデアが得られます。基本的に、関数の各戻りルートでチャネルを閉じる必要があります。実際、これはdeferステートメントの優れたユースケースです。

func Crawl(url string, depth int, fetcher Fetcher, ret chan string) {
    defer close(ret)
    if depth <= 0 {
        return
    }

    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        ret <- err.Error()
        return
    }

    ret <- fmt.Sprintf("found: %s %q", url, body)

    result := make([]chan string, len(urls))
    for i, u := range urls {
        result[i] = make(chan string)
        go Crawl(u, depth-1, fetcher, result[i])
    }

    for i := range result {
        for s := range result[i] {
            ret <- s
        }
    }

    return
}

func main() {
    result := make(chan string)
    go Crawl("http://golang.org/", 4, fetcher, result)

    for s := range result {
        fmt.Println(s)
    }
}

コードとの本質的な違いは、Crawlのすべてのインスタンスが独自のリターンチャネルを取得し、呼び出し元の関数がそのリターンチャネルで結果を収集することです。

于 2012-11-04T22:57:42.893 に答える
2

これが私のバージョンです(@fasmatの回答に触発されました)-これは、 RWMutexでカスタムキャッシュを利用することにより、同じURLを2回フェッチするのを防ぎます.

type Cache struct {
    data map[string]fakeResult
    mux sync.RWMutex
}

var cache = Cache{data: make(map[string]fakeResult)}

//cache adds new page to the global cache
func (c *Cache) cache(url string) fakeResult {
    c.mux.Lock()

    body, urls, err := fetcher.Fetch(url)
    if err != nil {
        body = err.Error()
    }

    data := fakeResult{body, urls}
    c.data[url] = data

    c.mux.Unlock()

    return data
}

//Visit visites the page at given url and caches it if needec
func (c *Cache) Visit(url string) (data fakeResult, alreadyCached bool) {
    c.mux.RLock()
    data, alreadyCached = c.data[url]
    c.mux.RUnlock()

    if !alreadyCached {
        data = c.cache(url)
    }

    return data, alreadyCached
}

/*
Crawl crawles all pages reachable from url and within the depth (given by args).
Fetches pages using given fetcher and caches them in the global cache.
Continously sends newly discovered pages to the out channel.
*/
func Crawl(url string, depth int, fetcher Fetcher, out chan string) {
    defer close(out)

    if depth <= 0 {
        return
    }

    data, alreadyCached := cache.Visit(url)
    if alreadyCached {
        return
    }

    //send newly discovered page to out channel
    out <- fmt.Sprintf("found: %s %q", url, data.body)

    //visit linked pages
    res := make([]chan string, len(data.urls))

    for i, link := range data.urls {
        res[i] = make(chan string)
        go Crawl(link, depth-1, fetcher, res[i])
    }

    //send newly discovered pages from links to out channel
    for i := range res {
        for s := range res[i] {
            out <- s
        }
    }
}

func main() {
    res := make(chan string)
    go Crawl("https://golang.org/", 4, fetcher, res)

    for page := range res {
        fmt.Println(page)   
    }
}

URL を 2 回取得しないことは別として、このソリューションは事前に総ページ数を知っているという事実を使用せず (任意の数のページで機能します)、タイマーによってプログラムの実行時間を誤って制限/延長しません。

于 2020-04-08T09:28:24.040 に答える
0

URL を 2 回クロールするのを避けるためにスライスを使用します。同時実行のない再帰バージョンは問題ありませんが、この同時実行バージョンについてはわかりません。

func Crawl(url string, depth int, fetcher Fetcher) {
    var str_arrs []string
    var mux sync.Mutex

    var crawl func(string, int)
    crawl = func(url string, depth int) {
        if depth <= 0 {
            return
        }

        mux.Lock()
        for _, v := range str_arrs {
            if url == v {
                mux.Unlock()
                return
            }
        }
        str_arrs = append(str_arrs, url)
        mux.Unlock()

        body, urls, err := fetcher.Fetch(url)
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Printf("found: %s %q\n", url, body)
        for _, u := range urls {
            go crawl(u, depth-1) // could delete “go” then it is recursive
        }
    }

    crawl(url, depth)
    return
}

func main() {
    Crawl("http://golang.org/", 4, fetcher)
}
于 2016-09-10T07:13:39.587 に答える