2012-09-27 7 views
6

は私が行くツアーexercise #71golang:私はそれは、それが正常に動作し<code>go run 71_hang.go ok</code>のように実行された場合

を試してみました()fmt.Printを追加しない限り、選択とgorouteは停止しません。

ただし、go run 71_hang.go nogoodを使用すると、永遠に実行されます。

唯一の違いは、selectステートメントのdefaultにあるfmt.Print("")です。

私には分かりませんが、無限ループと競合状態の疑いがありますか?そして、私の解決策はここにあります。

注:Goは、あなたのselectdefault文を置くthrow: all goroutines are asleep - deadlock!

package main 

import (
    "fmt" 
    "os" 
) 

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) 
} 

func crawl(todo Todo, fetcher Fetcher, 
    todoList chan Todo, done chan bool) { 
    body, urls, err := fetcher.Fetch(todo.url) 
    if err != nil { 
     fmt.Println(err) 
    } else { 
     fmt.Printf("found: %s %q\n", todo.url, body) 
     for _, u := range urls { 
      todoList <- Todo{u, todo.depth - 1} 
     } 
    } 
    done <- true 
    return 
} 

type Todo struct { 
    url string 
    depth int 
} 

// Crawl uses fetcher to recursively crawl 
// pages starting with url, to a maximum of depth. 
func Crawl(url string, depth int, fetcher Fetcher) { 
    visited := make(map[string]bool) 
    doneCrawling := make(chan bool, 100) 
    toDoList := make(chan Todo, 100) 
    toDoList <- Todo{url, depth} 

    crawling := 0 
    for { 
     select { 
     case todo := <-toDoList: 
      if todo.depth > 0 && !visited[todo.url] { 
       crawling++ 
       visited[todo.url] = true 
       go crawl(todo, fetcher, toDoList, doneCrawling) 
      } 
     case <-doneCrawling: 
      crawling-- 
     default: 
      if os.Args[1]=="ok" { // * 
       fmt.Print("") 
      } 
      if crawling == 0 { 
       goto END 
      } 
     } 
    } 
END: 
    return 
} 

func main() { 
    Crawl("http://golang.org/", 4, fetcher) 
} 

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

答えて

15

なかったとして、それがデッドロックではないですが選択した作品方法を変更します。デフォルトのステートメントがないと、selectはチャネル上のメッセージの待機をブロックします。デフォルトのステートメントでは、チャンネルから読み込むことがないたびにselectがデフォルトステートメントを実行します。あなたのコードでは、これが無限ループになると思います。 fmt.Printステートメントを入れると、スケジューラーは他のゴルーチンをスケジュールできます。

このようにコードを変更すると、他のゴルーチンが正常に動作するように非ブロック的な方法でselectを使用して正しく動作します。

for { 
     select { 
     case todo := <-toDoList: 
      if todo.depth > 0 && !visited[todo.url] { 
       crawling++ 
       visited[todo.url] = true 
       go crawl(todo, fetcher, toDoList, doneCrawling) 
      } 
     case <-doneCrawling: 
      crawling-- 
     } 
     if crawling == 0 { 
      break 
     } 
    } 

あなたはスケジューラが無限ループでビジーであることを別のヒントですGOMAXPROCS = 2を使用する場合は、あなたの元のコードの作業を行うことができます。

ゴルーチンは協調的にスケジュールされています。私があなたの問題について完全に理解していないのは、selectが、ゴルーチンが収穫すべきポイントだということです。他の誰かがあなたの例にない理由を説明できることを願っています。

+1

は*理由*デフォルト文を生成しない選択。「デフォルト」とGOMAXPROCSの説明を釘付けにしたので、あなたが完全に理解していないかどうかはわかりませんが。 – mna

+1

それはまさに私が知らなかったことです、ありがとう! –

+0

"デフォルトのステートメントのためにselectが生成されません。"私が知らないものです。ありがとう。 – Sungam

5

ほとんどの場合、デフォルトのケースが実行されるため、100%のCPU負荷があります。何度も繰り返し実行されるため、効果的に無限ループになります。この状況では、Goスケジューラは設計上、別のゴルーチンに制御を渡しません。だから、他のゴルーチンには決してcrawling != 0を設定する機会はありません。あなたは無限ループをしています。

私の意見では、デフォルトのケースを削除し、代わりにselectステートメントで再生したい場合は、別のチャンネルを作成する必要があります。

そうでない場合runtimeパッケージには、あなたが汚い道に行くのに役立ちます:、

  • runtime.GOMAXPROCS(2)が動作します(またはエクスポート= 2 GOMAXPROCS)を使用すると、複数の実行OSのスレッドを持つことになります。この方法を
  • コールruntime.Gosched()内部クロールが時々あります。 CPU負荷は100%ですが、別のGoroutineに明示的に制御を渡します。

編集:はい、とfmt.Printfが違いを作る理由:それは明示的にいくつかのシステムコールのものに制御を渡しますので...;)

+0

"この状況では、Goスケジューラは設計上、別のゴルーチンに制御を渡しません。完全に真実ではありません。 Go 1.0(?)スケジューラはこれを行いますが、スケジューラは不完全です。あなたがリストした回避策(または 'fmt.Println()'を呼び出すことによってシステムコールを行う)は、スケジューラを起動させます。 Go 1.2でのこれらの改善の詳細については、http://golang.org/doc/go1.2#preemptionを参照してください。 – ayke

関連する問題