Golang: goroute with select does not stop unless I added fmt.Print ()

I tried Go Tour exercise # 71

If it is run as go run 71_hang.go ok , it works fine.

However, if you use go run 71_hang.go nogood , it will work forever.

The only difference is the extra fmt.Print("") in default in the select statement.

I'm not sure, but I suspect some kind of endless cycle and race condition? And here is my decision.

Note. This is not a dead end since Go is not 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/", }, }, } 
+6
source share
2 answers

Bringing the default statement into your select changes the way you choose. Without a default selection, select blocks waiting for any messages on the channels. Using the default statement, select will run the default statement every time nothing is read from channels. In your code, I think this makes an infinite loop. Setting the fmt.Print instruction in permission of the scheduler to plan other mountains.

If you change your code like this, then it will work correctly, using select in non-blocking mode, which allows other goroutines to work correctly.

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

You can make your source code if you use GOMAXPROCS = 2, which is another hint that the scheduler is busy in an infinite loop.

Please note that goroutines are planned concurrently. What I don't quite understand about your problem is that select is the point at which goroutine should work. Hopefully someone else can explain why this is not in your example.

+15
source

You have 100% CPU utilization, because the default case will be executed almost all the time, which leads to an infinite loop, because it runs again and again. In this situation, the Go planner does not transfer control to another design vehicle. That way, any other goroutine can never set crawling != 0 , and you will have an infinite loop.

In my opinion, you should remove the default case and create another channel instead if you want to play with the select statement.

Otherwise, the runtime package will help you go the dirty way:

  • runtime.GOMAXPROCS(2) will work (or export GOMAXPROCS = 2), so you will have more than one OS execution thread
  • call runtime.Gosched() inside the scan from time to time. Despite the fact that the processor load is 100%, this will clearly transfer control to another Goroutine.

Edit: Yes, and the reason fmt.Printf matters: since it explicitly transfers control to some syscall files ...;)

+5
source

Source: https://habr.com/ru/post/926375/


All Articles