dongxian3574 2019-05-18 06:09
浏览 47
已采纳

一次退出所有递归生成的goroutine

I have a function that recursively spawns goroutines to walk a DOM tree, putting the nodes they find into a channel shared between all of them.

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

Which I'd call like

// 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
    }
}

I am wondering how I could quit all of these goroutines--i.e. close ch--once I have found a node of a certain type or some other condition has been fulfilled. I have tried using a quit channel that'd be polled before spawning the new goroutines and close ch if a value was received but that lead to race conditions where some goroutines tried sending on the channel that had just been closed by another one. I was pondering using a mutex but it seems inelegant and against the spirit of go to protect a channel with a mutex. Is there an idiomatic way to do this using channels? If not, is there any way at all? Any input appreciated!

  • 写回答

1条回答 默认 最新

  • dphs48626 2019-05-18 07:06
    关注

    The context package provides similar functionality. Using context.Context with a few Go-esque patterns, you can achieve what you need.

    To start you can check this article to get a better feel of cancellation with context: https://www.sohamkamani.com/blog/golang/2018-06-17-golang-using-context-cancellation/

    Also make sure to check the official GoDoc: https://golang.org/pkg/context/

    So to achieve this functionality your function should look more like:

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

    And when calling the function, you will have something like:

    // ...
    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
        }
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 如何用Labview在myRIO上做LCD显示?(语言-开发语言)
  • ¥15 Vue3地图和异步函数使用
  • ¥15 C++ yoloV5改写遇到的问题
  • ¥20 win11修改中文用户名路径
  • ¥15 win2012磁盘空间不足,c盘正常,d盘无法写入
  • ¥15 用土力学知识进行土坡稳定性分析与挡土墙设计
  • ¥70 PlayWright在Java上连接CDP关联本地Chrome启动失败,貌似是Windows端口转发问题
  • ¥15 帮我写一个c++工程
  • ¥30 Eclipse官网打不开,官网首页进不去,显示无法访问此页面,求解决方法
  • ¥15 关于smbclient 库的使用