同步通道和等待组的最佳实践是什么?

gau*_*uge 5 go

同步等待组和通道的最佳实践是什么?我想在循环中处理消息和阻塞,似乎将通道的关闭委托给另一个 go 例程似乎是一个奇怪的解决方案?

func Crawl(url string, depth int, fetcher Fetcher) {
    ch := make(chan string)

    var waitGroup sync.WaitGroup
    waitGroup.Add(1)
    go crawlTask(&waitGroup, ch, url, depth, fetcher)

    go func() {
        waitGroup.Wait()
        close(ch)
    }()

    for message := range ch {
        // I want to handle the messages here
        fmt.Println(message)
    }
}

func crawlTask(waitGroup *sync.WaitGroup, ch chan string, url string, depth int, fetcher Fetcher) {
    defer waitGroup.Done()

    if depth <= 0 {
        return
    }
    body, urls, err := fetcher.Fetch(url)

    if err != nil {
        ch <- err.Error()
        return
    }
    ch <- fmt.Sprintf("found: %s %q\n", url, body)
    for _, u := range urls {
        waitGroup.Add(1)
        go crawlTask(waitGroup, ch, u, depth-1, fetcher)
    }
}
func main() {
    Crawl("http://golang.org/", 4, fetcher)
}

// truncated from https://tour.golang.org/concurrency/10 webCrawler
Run Code Online (Sandbox Code Playgroud)

Nev*_*ore 4

作为使用 waitgroup 和额外 goroutine 的替代方案,您可以使用单独的通道来结束 goroutine

这也是 Go 中的惯用语。它涉及使用对照组进行阻止select

因此,您必须创建make一个新通道,通常使用一个空结构作为其值(例如closeChan := make(chan struct{}),当关闭 ( close(closeChan)) 时,该通道将结束 goroutine 本身。

您可以使用select来阻止,直到提供数据或关闭,而不是在a上进行范围。chan

中的代码Crawl可能如下所示:

for { // instead of ranging over a to-be closed chan
    select {
    case message := <-ch:
        // handle message
    case <-closeChan:
        break // exit goroutine, can use return instead
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在 中crawlTask,您可以关闭closeChan(作为另一个参数传入,就像ch您返回时一样(我想那就是您希望其他 goroutine 结束并停止处理消息的时候?)

if depth <= 0 {
    close(closeChan)
    return
}
Run Code Online (Sandbox Code Playgroud)