我有一个函数,该函数递归地生成goroutine,以遍历DOM树,将找到的节点放入它们之间共享的通道中。
import (
"golang.org/x/net/html"
"sync"
)
func walk(doc *html.Node, ch chan *html.Node) {
var wg sync.WaitGroup
defer close(ch)
var f func(*html.Node)
f = func(n *html.Node) {
defer wg.Done()
ch <- n
for c := n.FirstChild; c != nil; c = c.NextSibling {
wg.Add(1)
go f(c)
}
}
wg.Add(1)
go f(doc)
wg.Wait()
}
我想打个电话
// get the webpage using http
// parse the html into doc
ch := make(chan *html.Node)
go walk(doc, ch)
for c := range ch {
if someCondition(c) {
// do something with c
// quit all goroutines spawned by walk
}
}
我想知道如何退出所有这些goroutine,即关闭ch
-一旦我发现某种类型的节点或满足某些其他条件。我尝试使用一个quit
通道,该通道在产生新的goroutine之前会被轮询,如果收到一个值,则关闭ch
,但这会导致竞态条件,其中有些goroutine尝试在刚刚发送的通道上发送被另一个关闭。我当时正在考虑使用互斥锁,但它看起来不够优雅,并且违反了用互斥锁保护通道的精神。是否有惯用的方式使用渠道进行此操作?如果没有,那有什么办法吗?任何输入表示赞赏!
答案 0 :(得分:3)
context
软件包提供了类似的功能。将context.Context
与一些Go-esque模式结合使用,即可实现所需的功能。
首先,您可以查看本文,以更好地使用context
:https://www.sohamkamani.com/blog/golang/2018-06-17-golang-using-context-cancellation/
还要确保检查官方的GoDoc:https://golang.org/pkg/context/
因此,要实现此功能,您的功能应类似于:
func walk(ctx context.Context, doc *html.Node, ch chan *html.Node) {
var wg sync.WaitGroup
defer close(ch)
var f func(*html.Node)
f = func(n *html.Node) {
defer wg.Done()
ch <- n
for c := n.FirstChild; c != nil; c = c.NextSibling {
select {
case <-ctx.Done():
return // quit the function as it is cancelled
default:
wg.Add(1)
go f(c)
}
}
}
select {
case <-ctx.Done():
return // perhaps it was cancelled so quickly
default:
wg.Add(1)
go f(doc)
wg.Wait()
}
}
调用该函数时,您将看到类似以下内容的
:// ...
ctx, cancelFunc := context.WithCancel(context.Background())
walk(ctx, doc, ch)
for value := range ch {
// ...
if someCondition {
cancelFunc()
// the for loop will automatically exit as the channel is being closed for the inside
}
}