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

立即退出所有递归生成的goroutine,go,Go,我有一个递归生成goroutines以遍历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) {

我有一个递归生成goroutines以遍历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()
}

我想知道,一旦发现某个类型的节点或满足某些其他条件,如何退出所有这些goroutine(即关闭
ch
)。我曾尝试使用一个
quit
频道,该频道在生成新的goroutine之前会被轮询,如果收到一个值,则关闭
ch
,但这会导致一些goroutine尝试在刚刚被另一个goroutine关闭的频道上发送。我曾考虑过使用互斥体,但它似乎不雅观,违背了使用互斥体保护频道的精神。有没有一种惯用的方法使用频道来实现这一点?如果没有,有什么办法吗?欢迎任何意见

上下文包提供了类似的功能。使用
context.context
和一些Go-esque模式,您可以实现所需的功能

首先,您可以查看本文,通过
context
,获得更好的取消体验:

还要确保检查官方GoDoc:

因此,要实现此功能,您的功能应该更像:

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()
    }
}

太好了,谢谢!我记得第一次进入围棋时,我浏览了一下上下文包,但不太了解它的用途,这很有帮助。
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
    }
}