立即退出所有递归生成的goroutines

时间:2019-05-18 06:09:20

标签: go

我有一个函数,该函数递归地生成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尝试在刚刚发送的通道上发送被另一个关闭。我当时正在考虑使用互斥锁,但它看起来不够优雅,并且违反了用互斥锁保护通道的精神。是否有惯用的方式使用渠道进行此操作?如果没有,那有什么办法吗?任何输入表示赞赏!

1 个答案:

答案 0 :(得分:3)

context软件包提供了类似的功能。将context.Context与一些Go-esque模式结合使用,即可实现所需的功能。

首先,您可以查看本文,以更好地使用contexthttps://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
    }
}