在goroutine内部启动goroutine是否可接受?

huangapple go评论82阅读模式
英文:

Is launching goroutines inside of goroutines acceptable?

问题

我现在正在学习使用Go,并且我的第一个项目之一是一个简单的ping脚本。基本上,我想要ping一堆URL,并在每个URL的响应后等待XXX秒,然后再次ping。以下是简化后的代码:

func main() {
    // 读取我们的URL文本文件
    f, err := ioutil.ReadFile(urlFile)
    if err != nil {
        log.Print(err)
    }

    urlStrings := []string{}
    urlStrings = strings.Split(string(f), "\n")

    for _, v := range urlStrings {
        go ping(v)
    }

    // 将日志输出到终端
    // 通道是全局的
    for i := range c {
        fmt.Println(i)
    }
}

func ping(url string) {
    // 用于计时
    start := time.Now()

    // 发送请求
    _, err := http.Get(url)

    if err != nil {
        msg := url + " Error:" + err.Error()

        fmt.Println(msg)

        c <- msg
        reportError(msg)
    } else {
        lag := time.Since(start)
        var msg string

        // 运行缓慢
        if lag > lagThreshold*time.Second {
            msg = url + " lag: " + lag.String()
            reportError(msg)
        }

        msg = url + ", lag: " + lag.String()
        c <- msg
    }

    time.Sleep(pingInterval * time.Second)
    go ping(url) // 这样写可以吗?
}

在我的Get请求中,我之前调用了defer res.Body.Close(),但是在应用程序运行一段时间后会导致恐慌。我认为,直到goroutine被垃圾回收并且res不再存在,defer才能调用响应的Close()。

这让我想到,在goroutine内部调用另一个goroutine是否是最佳实践,或者我是否导致函数永远不会退出,然后只有在goroutine被垃圾回收时才会调用defer。

英文:

I'm learning Go right now using and one of my first projects is a simple ping script. Essentially I want to ping a bunch of urls, and on response of each one wait XXX number of seconds then ping again. Here is the abridged code:

func main() {
	//	read our text file of urls
	f, err := ioutil.ReadFile(urlFile)
	if err != nil {
		log.Print(err)
	}

	urlStrings := []string{}
	urlStrings = strings.Split(string(f), &quot;\n&quot;)

	for _, v := range urlStrings {
		go ping(v)
	}

	//	output logs to the terminal
	//	channel is global
	for i := range c {
		fmt.Println(i)
	}
}

func ping(url string) {
	//	for our lag timer
	start := time.Now()

	//	make our request
	_, err := http.Get(url)

	if err != nil {
		msg := url + &quot; Error:&quot; + err.Error()

		fmt.Println(msg)

		c &lt;- msg
		reportError(msg)
	} else {
		lag := time.Since(start)
		var msg string

		//	running slow
		if lag &gt; lagThreshold*time.Second {
			msg = url + &quot; lag: &quot; + lag.String()
			reportError(msg)
		}

		msg = url + &quot;, lag: &quot; + lag.String()
		c &lt;- msg
	}

	time.Sleep(pingInterval * time.Second)
	go ping(url) // is this acceptable?
}

On my Get request I was previously calling defer res.Body.Close() but that was panicing after the app ran for awhile. I assumed that the defer could not call the Close() on the response until the goroutine had been garbage collected and the res no longer existed.

That got me thinking if by calling a goroutine inside of a goroutine was best practice or if I as causing the function to never exit, and then a defer would only be called once the goroutine was garbage collected.

答案1

得分: 38

这是可以的。从另一个goroutine调用goroutine是完全可以接受的。调用的goroutine仍然会退出,而新的goroutine会继续执行。

英文:

That is fine. It's perfectly acceptable to call a goroutine from another goroutine. The calling goroutine will still exit and the new goroutine will go on it's merry way.

答案2

得分: 13

从一个goroutine中创建一个新的goroutine本身是完全可以的。

但是我怀疑这可能不是你的问题的最简单和最清晰的解决方案。
我猜你的第一个版本做了显而易见的事情,并在无限循环中ping每个URL。这会导致延迟调用失效:延迟调用在函数返回时执行。(这与goroutine被“垃圾回收”无关;实际上,goroutine只是结束,它们不会被回收)。在无限循环中,你永远不会返回,只会累积未执行的延迟调用。因此,你永远不会关闭所有打开的res.Body,最终导致内存不足/其他问题并出现panic。

使用defer res.Body.Close是一种不错的习惯用法,但不适用于无限循环。

我建议尝试你的第一个版本,并直接在nil错误路径上执行res.Body.Close。

英文:

Spaning a new goroutine from within a goroutine is per se perfectly fine.

But I doubt this is the simplest and cleanest solution to your issue.
I guess that your first version did the obvious thing and ping each URL in an endless loop. And this bites defer: Defer'ed calls are executed once the function returns. (This has nothing to do wit a goroutine beeing "garbage collected; actually goroutines just end, they are not collected). In an endless loop you never return, you just accumulate defered calls which get never executed. You thus never close all the open res.Body and you run out of memory/whatever and see a panic.

Doing defer res.Body.Close is a nice idiom, but not inside an endless loop.

I would try your first version and directly execute res.Body.Close on the nil error path.

huangapple
  • 本文由 发表于 2013年4月30日 04:58:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/16288116.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定