如果你知道golang通道的值是有限的,那么你应该消费所有的值吗?

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

Should I consume all of a golang channel's values if I know they are finite?

问题

我正在通过《A Tour of Go》中的并发部分进行学习,并且对于在Go中消费有限通道的正确惯例很感兴趣。在这个练习中,我需要从两个通道中读取值,并确定这些值是否相同且顺序相同。如果不是,我可以立即从我的方法中返回false。然而,如果我这样做,Go会自动清理我的通道吗,还是我的goroutine会永远挂起并消耗资源?

处理这种情况的最佳方法是将一个取消通道传递给我的goroutine,但由于goroutine读取有限数量的数据,似乎只需消费所有数据即可。在实际生活中,处理这种情况的最佳方法是什么?

英文:

I'm working through the Concurrency section of A Tour of Go, and I'm curious about proper Go convention for consuming finite channels. In this exercise, I need to read values from two channels and determine if the values are the same and in the same order. If not, I can immediately return false from my method. However, if I do that, will Go clean up my channels for me automatically, or will my goroutines be left hanging forever and consuming resources?

The best way to handle this would be to pass a cancel channel into my goroutines, but since the goroutines read a finite amount of data, it seems fine to just consume all the data. What is the best way to handle this case in real life?

答案1

得分: 3

Andrew Gerrand在Gophercon上的演讲中详细讨论了这个问题,具体在第37页

> 创建一个退出通道并将其传递给每个walker。当Same退出时关闭quit通道,任何正在运行的walker都会被终止。

func Same(t1, t2 *tree.Tree) bool {
    quit := make(chan struct{})
    defer close(quit)
    w1, w2 := Walk(t1, quit), Walk(t2, quit)
    for {
        v1, ok1 := <-w1
        v2, ok2 := <-w2
        if v1 != v2 || ok1 != ok2 {
            return false
        }
        if !ok1 {
            return true
        }
    }
}
英文:

Andrew Gerrand's talk at Gophercon covers this exact question on slide 37.

> Create a quit channel and pass it to each walker. By closing quit
> when the Same exits, any running walkers are terminated.

func Same(t1, t2 *tree.Tree) bool {
    quit := make(chan struct{})
    defer close(quit)
    w1, w2 := Walk(t1, quit), Walk(t2, quit)
    for {
        v1, ok1 := <-w1
        v2, ok2 := <-w2
        if v1 != v2 || ok1 != ok2 {
            return false
        }
        if !ok1 {
            return true
        }
    }
}

答案2

得分: 2

使用退出通道(quit channels)通常是一个好主意,正如在Go并发模式:管道和取消博客文章和Heath Borders的答案中所讨论的那样。

还有golang.org/x/net/context,在Go并发模式:上下文博客文章中讨论了它,它添加了超时、截止时间和其他功能。

然而,要直接回答问题:
> Go会自动清理我的通道吗?

这取决于通道的缓冲区以及通道的写入方式。

例如:

func writeValues(n int, c chan int) {
	for i := 0; i < n; i++ {
		c <- i
	}
	log.Println("writeValues", n, "done")
}

func main() {
	ch1 := make(chan int, 12)
	ch2 := make(chan int, 6)
	ch3 := make(chan int)

	go writeValues(10, ch1)
	go writeValues(11, ch2)
	go writeValues(12, ch3)

	time.Sleep(time.Second) // XXX
}

<kbd>Playground</kbd>

在这里,第一个goroutine将完成,并且ch1(以及其中缓冲的任何内容)将被垃圾回收和清理。
然而,后面两个goroutine将被阻塞,等待它们能够写入所有的值。垃圾回收器永远不会触及ch2ch3,因为被阻塞的goroutine保持对它们的引用。
请注意,如果从通道中读取的项少至少为五个,ch2将会被清理。

通常,只有在执行类似以下操作时才会依赖于此:

    errc := make(chan error, 1)
    go func() { errc <- someErrorReturningFunc() }()

如果被调用的函数没有取消的方式,那么这是一种常见的习惯用法。
您可以这样做,并在不从errc读取的情况下中止/提前返回,并且知道当函数最终返回时,goroutine和通道将被清理。
这里的errc通道的缓冲区大小很重要。

英文:

Using quit channels,
as discussed in the
Go Concurrency Patterns: Pipelines and cancellation
blog article
and Heath Borders' answer,
is often a good idea.

There is also the
golang.org/x/net/context package
discussed in the
Go Concurrency Patterns: Context
blog article which adds timeouts, deadlines, and other features.

However, to directly address:
> will Go clean up my channels for me automatically

It depends on the channel buffering and how the channels are written to.

E.g.

func writeValues(n int, c chan int) {
	for i := 0; i &lt; n; i++ {
		c &lt;- i
	}
	log.Println(&quot;writeValues&quot;, n, &quot;done&quot;)
}

func main() {
	ch1 := make(chan int, 12)
	ch2 := make(chan int, 6)
	ch3 := make(chan int)

	go writeValues(10, ch1)
	go writeValues(11, ch2)
	go writeValues(12, ch3)

	time.Sleep(time.Second) // XXX
}

<kbd>Playground</kbd>

Here the first goroutine will complete and ch1 (and anything buffered in it) would be garbage collected and cleaned up.
However, the later two goroutines will block waiting until they can write all their values. The garbage collector would never touch ch2 and ch3 since the blocked goroutines keep a reference to them.
Note ch2 would get cleaned up if as few as five items where read from the channel.

Usually, you only rely on this when doing something like:

    errc := make(chan error, 1)
    go func() { errc &lt;- someErrorReturningFunc() }()

If the function being called has no way to cancel it, then this is a common idiom.
You can do this and abort/return early without reading from errc and know that the goroutine and channel will be cleaned up when the function eventually returns.
The buffer size of the errc channel is important here.

huangapple
  • 本文由 发表于 2014年12月28日 13:39:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/27673952.html
匿名

发表评论

匿名网友

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

确定