2017-12-31 202 views
1

私はチュートリアルのすべてのツアーをやろうとしていましたが、私はウェブクローラで立ち往生しています。私はそれを終了したと思ったが、出力が矛盾していて、理由を把握するのに十分な並行性の経験がない。ここでウェブクローラが別の出力に同じコードを出力する

は私のコードです:

package main 

import (
    "fmt" 
    "sync" 
) 

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) 
} 
var cache = struct { 
    fetched map[string]bool 
    sync.Mutex 
}{fetched: make(map[string]bool)} 

// Crawl uses fetcher to recursively crawl 
// pages starting with url, to a maximum of depth. 
func Crawl(url string, depth int, fetcher Fetcher, c chan []string, quit chan int) { 
    if depth <= 0 { 
     return 
    } 
    go safeVisit(url, c, quit, fetcher) 
    for { 
     select { 
     case <- quit: 
      return 
     case u:= <-c: 
      for _, v:= range u { 
       go Crawl(v, depth -1, fetcher, c, quit) 
      } 
     } 
    } 
} 
func main() { 
    c := make(chan []string) 
    quit := make(chan int) 
    Crawl("http://golang.org/", 4, fetcher, c, quit) 
} 

func safeVisit(url string, c chan []string, quit chan int, fetcher Fetcher) { 
    cache.Lock() 
    defer cache.Unlock() 
    if _, ok := cache.fetched[url] ; ok { 
     quit <- 0 
     return 
    } 
    body, urls, err := fetcher.Fetch(url) 
    cache.fetched[url] = true 
    if err != nil { 
     fmt.Println(err) 
     return 
    } 
    fmt.Printf("Visited : %s, %q \n", url, body) 
    c <- urls 

} 

// 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/", 
     }, 
    }, 
} 

はここで最後のパッケージが

Visited : http://golang.org/, "The Go Programming Language" 
not found: http://golang.org/cmd/ 
Visited : http://golang.org/pkg/, "Packages" 
Visited : http://golang.org/pkg/os/, "Package os" 

そして最後に(故意に上記のアスタリスクに)欠けているいくつかのサンプル出力最初とは異なる

Visited : http://golang.org/, "The Go Programming Language" 
not found: http://golang.org/cmd/ 
Visited : http://golang.org/pkg/, "Packages" 
Visited : http://golang.org/pkg/os/, "Package os" 
**Visited : http://golang.org/pkg/fmt/, "Package fmt"** 

Process finished with exit code 0 

です、いくつかの実行でデッドロックさえあります:

Visited : http://golang.org/, "The Go Programming Language" 
not found: http://golang.org/cmd/ 
Visited : http://golang.org/pkg/, "Packages" 
Visited : http://golang.org/pkg/os/, "Package os" 
Visited : http://golang.org/pkg/fmt/, "Package fmt" 
fatal error: all goroutines are asleep - deadlock! 

goroutine 1 [select]: 
main.Crawl(0x4bfdf9, 0x12, 0x4, 0x524220, 0xc420088120, 0xc420092000, 0xc420092060) 
    /home/kostas/development/challenges/go/helloWorld.go:26 +0x201 
main.main() 
    /home/kostas/development/challenges/go/helloWorld.go:39 +0xab 

goroutine 23 [select]: 
main.Crawl(0x4bfdf9, 0x12, 0x3, 0x524220, 0xc420088120, 0xc420092000, 0xc420092060) 
    /home/kostas/development/challenges/go/helloWorld.go:26 +0x201 
created by main.Crawl 
    /home/kostas/development/challenges/go/helloWorld.go:31 +0x123 

goroutine 24 [select]: 
main.Crawl(0x4c09f9, 0x16, 0x3, 0x524220, 0xc420088120, 0xc420092000, 0xc420092060) 
    /home/kostas/development/challenges/go/helloWorld.go:26 +0x201 
created by main.Crawl 
    /home/kostas/development/challenges/go/helloWorld.go:31 +0x123 

goroutine 5 [select]: 
main.Crawl(0x4bfdf9, 0x12, 0x3, 0x524220, 0xc420088120, 0xc420092000, 0xc420092060) 
    /home/kostas/development/challenges/go/helloWorld.go:26 +0x201 
created by main.Crawl 
    /home/kostas/development/challenges/go/helloWorld.go:31 +0x123 

goroutine 6 [select]: 
main.Crawl(0x4c0a0f, 0x16, 0x3, 0x524220, 0xc420088120, 0xc420092000, 0xc420092060) 
    /home/kostas/development/challenges/go/helloWorld.go:26 +0x201 
created by main.Crawl 
    /home/kostas/development/challenges/go/helloWorld.go:31 +0x123 

私はそれが並行性と再帰と関係があると仮定します。待っているグループなどを使用するgit hubで他のソリューションを見たことがありますが、チュートリアルでは使用されていません。

UPDATE

私が起こっていると問題に取り組んでいるものを考え出しました。基本的には、チャネルが終了してcが必ずしも期待通りの順序で実行されないため、select文が無限ループに陥ることがあります。私はプリントする(「何もしない」)デフォルトのケースを追加しました。プログラムは時には永遠にループし、時には運行によって正しい方法で実行されることもあります。私の終了条件が正しくない

+0

デッドロックはどのくらいですか? 1で10?私はそれを再現しようとしています。 –

+0

それはかなり矛盾しています。ときには3時に1時に1時には10時に1時には2列で –

+0

この特定の問題の最も一般的な原因は、チャンネルが閉じられていないことです。たとえば、ここでは:https://stackoverflow.com/questions/12398359/throw-all-goroutines-are-asleep-deadlock - 私は周波数を測定しようとしていると、おそらく解決策を提案します。専門家ではないので、保証はありません。 –

答えて

2

私はこのケースがかなり明らかだと思います。あなたのチャンネルは騒がしいです。複数のゴルーチンが同じチャンネルから受信しており、ゴランはランダムに1つだけを選択します。

0からquitまでの0を送信すると、ゴルーチンの終了を知ることは決してありません。ゴーインテンの終了はランダムに選択されます。 cから受信する前に、新たに生成されたクロールがquitから受信された可能性があります(両方のチャネルが準備完了している場合でも)。

depthは混乱しており、番号safeVisitが不安定になり、quitが異なる(ランダムに)信号を発信します。生成されたすべてのゴルーチンを終了するだけでは不十分であり、デッドロックです。

編集:

まず、自分の仕事が何かを理解する必要があります。 Crawl機能は、URL、DEPとフェッチャに取り、そしてそれは:

  1. dep-1
でフェッチされたURLから生成された新しい Crawlキューを作成したURL
  • 印刷フェッチされたボディを取得します

    ツアーでは、parellelでurlを取得するように求められていますが、ステップ1の後にステップ2とステップ3を実行する必要があります。つまり、単一のクロールでフェッチを待つのが普通です。つまり、Fetchに電話するのは新しいgoroutineの必要はありません。

    ステップ3では、それぞれ新しいCrawlコールは前の処理を待つ必要がないため、これらのコールはparellelでなければなりません。これらの分析では

    、1は、これらのコードに来ることができます訪問したURLを扱う:

    func Crawl(url string, depth int, fetcher Fetcher) { 
        // TODO: Fetch URLs in parallel. 
        // TODO: Don't fetch the same URL twice. 
        // This implementation doesn't do either: 
        if depth <= 0 { 
         return 
        } 
        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, fetcher) 
        } 
        return 
    } 
    

    もう一つ問題があります。 quitを送信する代わりに、正しく完了しました。func(string) boolにして、直接電話:if Visited(Url) { return }とするだけです。

    サイドノート:ツアーは実際には教授法をよく教えていません。 golang concurencyパターンのようなブログ記事を見たり、コミュニケーションによってメモリを共有することができます。

  • +0

    これは正しいです、どうすればこれを修正するつもりですか?私は並行性の要素にかなり新しいので、私は考え方を変えようと苦労しています –

    +0

    私はそれにいくつかの考えを書こうとし、答えにそれを編集しました。私はそれが助けて欲しい。 –

    +0

    答えとヒントをありがとう、私はそれを把握し、いくつかの時間を過ごすと、明日見て、答えを受け入れるでしょう。明けましておめでとうございます –

    関連する問題