为了账号安全,请及时绑定邮箱和手机立即绑定

golang:带有select的goroute不会停止,除非我添加了fmt.Print()

golang:带有select的goroute不会停止,除非我添加了fmt.Print()

Go
慕仙森 2021-05-12 11:09:39
尝试了71号Go Tour练习如果运行像go run 71_hang.go ok,则可以正常运行。但是,如果使用go run 71_hang.go nogood,它将永远运行。唯一的区别是额外fmt.Print("")的default的select语句。我不确定,但是我怀疑某种无限循环和竞争条件吗?这是我的解决方案。注意:这不是死锁,因为Go没死 throw: all goroutines are asleep - deadlock!package mainimport (    "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]*fakeResulttype fakeResult struct {    body string    urls []string}
查看完整描述

2 回答

?
30秒到达战场

TA贡献1828条经验 获得超6个赞

default在选择select更改的方式中添加一条语句。如果没有默认语句,则select将阻止等待通道上的任何消息。使用默认语句时,select将在每次从通道中读取无内容时运行默认语句。在您的代码中,我认为这会造成无限循环。把fmt.Print在声明中允许调度安排其它够程。


如果您像这样更改代码,则它可以正常工作,并以非阻塞方式使用select,这将允许其他goroutines正常运行。


    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,则可以使原始代码正常工作,这是调度程序在无限循环中忙碌的另一个提示。


请注意,goroutine是合作安排的。我对您的问题不完全了解,这select是goroutine应该产生的点-我希望其他人可以解释为什么您的示例中没有它。


查看完整回答
反对 回复 2021-05-17
?
ibeautiful

TA贡献1993条经验 获得超5个赞

您拥有100%的CPU负载,因为几乎所有情况下都会执行默认情况,这会导致无限循环,因为它会一遍又一遍地执行。在这种情况下,Go调度程序在设计上不会将控制权交给另一个goroutine。因此,任何其他goroutine将永远没有机会进行设置,crawling != 0而您将遇到无限循环。

我认为,如果要使用select语句,则应删除默认情况,而应创建另一个通道。

否则,运行时程序包将帮助您走脏路:

  • runtime.GOMAXPROCS(2) 将起作用(或导出GOMAXPROCS = 2),这样您将拥有多个OS线程执行

  • runtime.Gosched()不时致电内部抓取。即使CPU负载为100%,这也将明确地将控制权传递给另一个Goroutine。

编辑:是的,以及fmt.Printf之所以有所作为的原因:因为它明确地将控制权传递给某些syscall东西...;)


查看完整回答
反对 回复 2021-05-17
  • 2 回答
  • 0 关注
  • 222 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信