2017-12-31 237 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. 기본적으로 select 문은 채널이 종료되고 c가 항상 예상 된 순서대로 실행되지 않기 때문에 끝없는 루프에서 멈추게됩니다. 나는 print ("할 일이 없다")라는 기본 사례를 추가했으며 때로는 프로그램이 영원히 반복되어 가끔씩 올바른 방식으로 행운을 빕니다. 내 출구 조건이 올바르지 않습니다.

+0

교착 상태는 얼마나 자주 발생합니까? 1에 10? 나는 그것을 재현하려고 노력하고있다. –

+0

꽤 일관성이 없습니다. 경우에 따라 1/3 때때로 1/10/경우에 따라 –

+0

이 특정 문제의 가장 일반적인 원인은 폐쇄되지 않는 채널입니다. 예를 들면 다음과 같습니다. https://stackoverflow.com/questions/12398359/throw-all-goroutines-are-asleep-deadlock- 나는 빈도를 측정하려고 노력하고 있으며 아마도 해결책을 제안 할 것입니다. 전문가는 아니므로 아무런 보장이 없습니다. –

답변

2

사례가 매우 분명하다고 생각합니다. 채널이 엉망입니다. 여러 개의 goroutine이 같은 채널에서 수신 중이며 golang은 무작위로 하나를 선택합니다.

quit을 통해 0을 보내면 어떤 goroutine 종료를 알 수 없습니다. 이동 예약자가 무작위로 선택합니다. 새로 생성 된 크롤은 에서 수신하기 전에 quit에서 수신 된 것일 수 있습니다 (두 채널이 모두 준비된 경우에도 해당).

그로 인해 depth은 엉망이며 safeVisit의 번호가 불안정 해지고 결과적으로 quit이 다른 (임의적으로) 신호를 발행합니다. 때로는 생성 된 모든 goroutine을 종료하는 것만으로는 충분하지 않으며, 교착 상태에 빠지기도합니다.

편집 :

먼저 작업 내용을 이해해야합니다. Crawl 함수는 URL을하는 출발과 페처에 소요, 그것은 :

  1. dep-1
으로 인출 된 URL에서 생성 된 새로운 Crawl 큐를 확인하는 URL
  • 인쇄 인출 된 몸을 가져 오기

    투어가 parellel에서 "가져 오기"를 요청했지만 1 단계 후에 2 단계와 3 단계를 수행해야합니다. 이는 단일 크롤링이 가져 오기를 대기하는 것이 정상임을 의미합니다. 즉, 새 goroutine을 호출 할 필요가 없습니다. Fetch.

    그리고 3 단계에서 각각 새로운 Crawl 호출은 이전 호출을 기다릴 필요가 없으므로 이러한 호출은 parellel이어야합니다. 이러한 분석

    , 하나는 이러한 코드에 올 수 : 방문 된 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 }이되었다.

    사이드 노트 : 투어는 실제로 concurency를 가르치는 데 좋지 않습니다. 당신은 블로그 기사를 보길 원할 것입니다, 예를 들어 골란 어구 패턴이나 의사 소통을 통해 기억을 공유하십시오.

  • +0

    맞습니다. 어떻게 수정 하시겠습니까? 나는 동시성의 요소에 상당히 익숙해 져서 생각의 방식을 바꿔 가려고 애 쓰고있다. –

    +0

    나는 그것에 대한 생각을 적어서 대답으로 편집하려고 노력했다. 나는 그것이 도움이되기를 바랍니다. –

    +0

    답변과 팁을 보내 주셔서 감사합니다. 내일 볼 수 있으며, 일단 알아 내고 시간을 보내면 답변을 수락합니다. 새해 복 많이 받으세요 –