返回通道 Golang

19

我正在尝试使用Go通道,并对来自Go博客的以下函数示例感到困惑:

func gen(nums []int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    fmt.Println("return statement is called ")
    return out
}

主要:

func main() {
    c := make(chan int)
    c = gen([]int{2, 3, 4, 5})

    // Consume the output.
    // Print 2,3,4,5
    fmt.Println(<-c)
    fmt.Println(<-c)
    fmt.Println(<-c)
    fmt.Println(<-c)
}

完整的代码:http://play.golang.org/p/Qh30wzo4m0

我的疑惑:

  1. 我理解的是,一旦调用了return语句,函数就会终止,该函数内部的通道将无法再被使用。

  2. return语句只会被调用一次。但是out通道的内容会被多次读取。在这种情况下,实际的执行流程是什么?

(我对并发编程还不太熟悉。)

3个回答

19
out := make(chan int)

这不是一个带有缓冲的通道,这意味着 out <- n 将会阻塞直到某个地方有人读取了该通道(fmt.Println(<-c) 的调用)
(另请参见“do golang channels maintain order”)

因此,在 gen() 函数末尾的返回并不意味着字面上的 go func() 已经终止(因为它仍在等待读取 out 通道的读取器消费其内容)。

但是 main 函数从 gen() 函数获取 out 通道作为返回值。
gen() 终止后如何获取它?

gen() 终止对其返回值(即 out 通道)没有影响:“gen()” 的目标是 “生成” 那个 out 通道。

main 可以在 gen() 终止之后使用 out(作为 gen() 的返回值)。

即使 gen() 终止了,gen() 中的 go func 字面量仍在运行。


正如vagabond评论中所指出的:

gen() 返回时,仍然存在对 out 通道的引用,这使得它不会被垃圾回收。
无论 gen() 是否有一个使用该通道的 go routine 闭包都没有关系。

当通道未被使用时,发送方可以显式地关闭通道。
然后通道的 select 将使 go routine 退出。
最后,所有内容都将被清除。


1
gen 可能已经终止,但它内部的文字 go func 仍然存在,作为一个独立的 goroutine。 - VonC
当 gen() 返回时,仍然有一个对于 out 通道的引用,这使得它不会被垃圾回收。无论 gen() 是否具有使用该通道的 Go 协程闭包都没有关系。 - zztczcx
@vagabond 我同意。有没有什么方法可以避免这种情况? - VonC
@VonC,通常情况下你不需要担心垃圾回收,因为Go语言会自动处理。当通道不再使用时,发送者可以显式关闭该通道。此时,与该通道相关的select将使Go协程退出,最终一切都将被清理干净。 - zztczcx
@vagabond 谢谢你。我已经将你的评论包含在答案中以增加可见性。 - VonC
显示剩余4条评论

4

因为gen()会将通道填充函数作为goroutine启动;

go func() {
    for _, n := range nums {
        out <- n
    }
    close(out)
}()

当第一个值被发送到out通道时,它会阻塞,因为没有接收者(未缓存通道在发送时会阻塞,直到有接收者)。那个goroutine不会在gen()函数返回时结束。在main()中从c接收。
fmt.Println(<-c)
...

然后导致在gen()中启动的goroutine继续填充通道,直到结果被读取完毕,最后main()在goroutine返回时返回,因为在out上没有剩余要发送的内容,在 c 上也没有剩余要接收的内容。

另外,main()中的c:= make(<-chan int)是不必要的,因为gen()创建并返回了一个通道。

请参见Playground


@Internet,我明白你的观点:“当gen()函数返回时,goroutine并不会结束。” 但是主函数从gen()函数中获取输出通道作为返回值。在gen()终止后如何获得它呢?这是我的疑问...如果我问了愚蠢的问题,请原谅,我还是个新手... - faisal_kk
@faisalkk gen() 函数返回通道,然后结束。该通道并不是由该函数“拥有”的,它只是被创建并返回(可以将 goroutine 视为一个单独的“后台”函数,通过使用 go 关键字来“解除”其所有权)。在这一点上,gen() 函数可以自由地结束,通道可以被其他函数使用。 - Intermernet

-2
我认为这就是您想要的: https://play.golang.org/p/WSBUPPyQiY6
package main

import (
    "fmt"
    "sync"
    "time"
)

func read() (elemChan chan int) {
    elemChan = make(chan int)
    go func() {
        for k := 0; k < 1000; k++ {
            elemChan <- k
        }
        close(elemChan)
    }()
    return
}

func main() {
    fmt.Println("Hello, playground")
    elemChan := read()
    wg := sync.WaitGroup{}
    for k := 0; k < 2; k++ {
        wg.Add(1)
        go func(k int) {
            for {
                e, more := <-elemChan
                if !more {
                    wg.Done()
                    return
                }
                fmt.Printf("goroutine #%d: %d\n", k, e)
                time.Sleep(1000 * time.Nanosecond)
            }
        }(k)
    }
    wg.Wait()
}

其实这是一个关于并行/并发执行流程如何发生的疑问,而且已经得到了很好的回答。请添加足够的注释或解释,而不是在这里粘贴代码片段。 - faisal_kk
我相信提供一个代码示例,展示如何返回一个开放的通道供其他消费者使用,对社区会有所帮助。这个帖子中的其他用户已经详细评论和回答了问题,我认为唯一还缺少的就是一个可行的示例。 - Grana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接