Golang中如何显式关闭通道

Golang中如何显式关闭通道 以下示例无论是否显式关闭通道都能正常工作(虽然不确定这是否是最佳实践)。然而,我想知道的是,显式关闭通道是否有任何价值或好处,还是应该留给内存管理器来处理/关闭它们?

一些资料令人困惑,并提出了类似的评论,例如:

  • 完成使用后,关闭你打开的通道。
  • 不必过于担心关闭通道,因为 Go 擅长处理这类事情。

我强烈感觉最好关闭它们,因为我读过一本书中提到:“通道会带来开销并对性能产生影响……通道是 Go 程序中内存管理问题的最大来源。

注意:我知道可以使用“超时”来退出循环,但这里特意使用了 done 通道。

package main

import (
	"fmt"
)

func main() {
	fmt.Println("main: begin")

	names := []string{"John", "Robert", "Al", "Rick"}

	msg := make(chan string)
	done := make(chan bool)

	go greet(msg, done, names)

Loop:
	for {
		select {
		case m := <-msg:
			fmt.Println(m)
		case <-done:
			fmt.Println("main: done")
			//close(done)
			break Loop
		}
	}

	fmt.Println("main: end")
}

func greet(msg chan<- string, done chan<- bool, name []string) {
	for _, name := range name {
		msg <- fmt.Sprintf("Hi %s!", name)
	}

	//close(msg)

	done <- true
}
main: begin
Hi John!
Hi Robert!
Hi Al!
Hi Rick!
main: done
main: end

更多关于Golang中如何显式关闭通道的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

您的评论非常棒,非常感谢。学习仍在进行中……

更多关于Golang中如何显式关闭通道的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


也许你也会觉得这篇文章很有用 —— 如何优雅地关闭通道

太棒了。谢谢。我记得之前看到过 range 的用法,但完全忘记了。现在,是时候等待关于“关闭还是不关闭通道”这个问题的评论了。

我之前读过,但觉得里面内容太多了。我有点迷失方向了。

你介意具体告诉我应该如何重构它吗?因为当我按照你的建议去做时,它要么打印一次就退出,要么根本不退出。我敢肯定我漏掉了什么。

嗯,这完全没问题 - https://stackoverflow.com/questions/8593645/is-it-ok-to-leave-a-channel-open

但是使用 close 可以让垃圾回收器更早地释放内存,并且你的代码会变得更易读,所以我个人总是显式地关闭通道。

请注意,在你的示例中,一个单独的通道 msg 就足够了。一旦 greet 调用了 close(msg),读取值的 for 循环 m := <-msg 就会结束。第二个通道 doneselect 语句是不必要的。

但我猜这不是你问题的重点 😉

func main() {
    fmt.Println("hello world")
}

奇怪,通常他们的文章都非常好且足够简单。

至于重构:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("main: begin")

	names := []string{"John", "Robert", "Al", "Rick"}

	msg := make(chan string)

	go greet(msg, names)

	for m := range msg {
		fmt.Println(m)
	}

	fmt.Println("main: end")
}

func greet(msg chan<- string, names []string) {
	for _, name := range names {
		msg <- fmt.Sprintf("Hi %s!", name)
	}
	close(msg)
}

https://play.golang.org/p/75vASSZdAbf

可以尝试类似这样的方法:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("main: begin")

	names := []string{"John", "Robert", "Al", "Rick"}

	msg := make(chan string)

	go greet(msg, names)

	// A: 在通道上使用 range 将循环直到通道关闭。
	for m := range msg {
		fmt.Println(m)
	}

	fmt.Println("main: end")
}

func greet(msg chan<- string, name []string) {
	for _, name := range name {
		msg <- fmt.Sprintf("Hi %s!", name)
	}

	// 在此处关闭 msg 将终止 A 处的循环。
	close(msg)
}

参见 https://play.golang.com/p/864dawlHgiD

在Go语言中,显式关闭通道是重要的内存管理实践,特别是在涉及goroutine通信和资源清理的场景中。以下是关键点分析:

显式关闭通道的价值

  1. 避免goroutine泄漏:未关闭的通道可能导致接收goroutine永久阻塞
  2. 释放资源:通道占用内存,及时关闭有助于垃圾回收
  3. 信号机制:关闭通道可作为广播信号通知多个接收者

示例代码分析

您的示例中需要显式关闭通道,原因如下:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("main: begin")

	names := []string{"John", "Robert", "Al", "Rick"}

	msg := make(chan string)
	done := make(chan bool)

	go greet(msg, done, names)

Loop:
	for {
		select {
		case m, ok := <-msg:  // 检查通道状态
			if !ok {
				fmt.Println("msg channel closed")
				continue
			}
			fmt.Println(m)
		case <-done:
			fmt.Println("main: done")
			close(msg)  // 显式关闭通道
			break Loop
		}
	}

	fmt.Println("main: end")
}

func greet(msg chan<- string, done chan<- bool, name []string) {
	for _, name := range name {
		msg <- fmt.Sprintf("Hi %s!", name)
	}

	close(msg)  // 发送完成后关闭通道

	done <- true
	close(done)  // 关闭done通道
}

必须关闭通道的场景

  1. range循环:使用range遍历通道时必须关闭
func process(ch chan int) {
    for v := range ch {  // 需要通道关闭才能退出循环
        fmt.Println(v)
    }
}
  1. 多个接收者:关闭通道作为广播信号
func broadcast(ch chan struct{}) {
    close(ch)  // 所有接收者都会立即收到零值
}
  1. select中的default:避免goroutine阻塞
select {
case <-ch:
    // 处理数据
default:
    // 如果ch未关闭且无数据,会执行default
}

性能影响分析

通道确实有开销:

  • 每个通道占用约96字节内存
  • 涉及锁操作和调度器交互
  • 大量未关闭的通道会增加GC压力

最佳实践建议

  1. 发送者负责关闭:谁创建/发送谁关闭
  2. 关闭后不再发送:panic: send on closed channel
  3. 不需要关闭的情况
    • 通道作为函数参数且生命周期明确
    • 简单的同步信号(如done通道)

在您的示例中,应该关闭msg通道,因为:

  • 发送完成后不再使用
  • 避免潜在的goroutine阻塞
  • 明确的资源清理

不关闭通道虽然在某些简单场景下可能正常工作,但在生产环境中可能导致难以调试的内存泄漏和goroutine泄漏问题。

回到顶部