7

Go Tourエクササイズ #71をやってみた

のようgo run 71_hang.go okに実行すると、正常に動作します。

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

唯一の違いは、ステートメントfmt.Print("")内のエクストラです。defaultselect

よくわかりませんが、ある種の無限ループと競合状態が疑われますか? そして、これが私の解決策です。

注: Go がそうしなかったので、デッドロックではありません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/",
        },
    },
}
4

2 に答える 2

16

select の動作方法を変更するdefaultステートメントを入れます。selectデフォルトのステートメントがないと、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 を使用すると、元のコードを機能させることができます。これは、スケジューラが無限ループでビジーであることを示すもう 1 つのヒントです。

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

于 2012-09-27T08:43:00.120 に答える
5

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

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

それ以外の場合、ランタイムパッケージは汚い道を行くのに役立ちます。

  • runtime.GOMAXPROCS(2)動作します (または GOMAXPROCS=2 をエクスポートします)。この方法では、複数の OS スレッドを実行できます。
  • runtime.Gosched()ときどきクロール内を呼び出します。CPU 負荷が 100% であっても、これにより明示的に別のゴルーチンに制御が渡されます。

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

于 2012-10-03T16:05:43.760 に答える