同步频道和等待组的最佳做法是什么?

时间:2016-12-17 16:09:04

标签: 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

2 个答案:

答案 0 :(得分:2)

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

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

所以你必须make一个新的频道,通常有一个空的结构作为它的值(例如closeChan := make(chan struct{}),当它关闭时(close(closeChan))会结束goroutine本身。

您可以使用选择来屏蔽,直到输入数据或关闭数据,而不是范围超过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
    }
}

然后在crawlTask中,您可以关闭closeChan(当您返回时,我会将ch作为另一个参数传入(当我想要另一个时,我会想到这一点) goroutine结束,并停止处理消息?)

if depth <= 0 {
    close(closeChan)
    return
}

答案 1 :(得分:1)

使用单独的'close'go-routine可以防止死锁。

如果等待/关闭操作在for-range循环之前位于主go-routine中,它将永远不会结束,因为所有'worker'go-routine将在通道上没有接收器时阻塞。如果它在for-range循环之后被放置在主go-routine中,那么它将无法访问,因为循环将阻塞而没有人关闭通道。

这个解释借鉴了“The Go Programming Language”一书(8.5并行循环)。