Go channel 关闭时的广播式通知特性
目录
在 Go 中,关闭 channel(close(ch))会向所有正在或即将从该 channel 接收数据的 goroutine 发送广播信号,使得它们能够立即感知到 channel 的关闭状态。这种机制常用于并发编程中的任务取消、资源回收、协程退出等场景。
1. 广播式通知的核心机制
(1) 所有阻塞的接收者被唤醒
- 当多个 goroutine 阻塞在
<-ch操作上时,调用close(ch)会立即唤醒所有阻塞的接收者,并让它们继续执行后续逻辑。 - 接收者会得到该 channel 类型的零值(如
int返回0,string返回"",指针返回nil等)。
(2) 接收操作返回 (value, ok) 中的 ok=false
- 使用
v, ok := <-ch时,如果 channel 已关闭,ok会变为false,表示不再有数据可读。 - 如果仅使用
v := <-ch,关闭后仍然会返回零值,但无法直接判断 channel 是否关闭。
(3) for-range 循环自动检测关闭
for v := range ch会在 channel 关闭后自动退出循环,无需手动检查ok。
2. 代码示例
示例 1:基本广播通知
ch := make(chan int)
// 启动 3 个接收者
for i := 0; i < 3; i++ {
go func(id int) {
for {
v, ok := <-ch
if !ok {
fmt.Printf("goroutine %d: channel closed\n", id)
return
}
fmt.Printf("goroutine %d: received %d\n", id, v)
}
}(i)
}
// 发送数据
ch <- 1
ch <- 2
// 关闭 channel(广播通知)
close(ch)
time.Sleep(time.Second) // 等待 goroutines 退出
输出(顺序可能不同):
goroutine 0: received 1
goroutine 0: received 2
goroutine 1: channel closed
goroutine 2: channel closed
goroutine 0: channel closed
所有阻塞的接收者都会在 close(ch) 后立即被唤醒,并检测到 ok=false,从而退出。
示例 2:for-range 自动检测关闭
ch := make(chan string)
go func() {
for v := range ch { // 自动检测 channel 关闭
fmt.Println("received:", v)
}
fmt.Println("channel closed, goroutine exits")
}()
ch <- "hello"
ch <- "world"
close(ch) // 广播通知接收者
time.Sleep(time.Second)
输出:
received: hello
received: world
channel closed, goroutine exits
for-range 会在 channel 关闭后自动退出循环。
示例 3:结合 select 处理关闭
ch := make(chan int)
done := make(chan struct{})
go func() {
for {
select {
case v, ok := <-ch:
if !ok {
fmt.Println("channel closed, exiting...")
close(done)
return
}
fmt.Println("received:", v)
case <-time.After(500 * time.Millisecond):
fmt.Println("no data, waiting...")
}
}
}()
ch <- 1
ch <- 2
close(ch) // 广播通知
<-done // 等待 goroutine 退出
输出:
received: 1
received: 2
channel closed, exiting...
select 会立即检测到 channel 关闭,并执行对应的逻辑。
3. 广播式通知的应用场景
(1) 协程退出通知
func worker(stopCh <-chan struct{}) {
for {
select {
case <-stopCh: // 收到关闭信号
fmt.Println("worker stopped")
return
default:
fmt.Println("working...")
time.Sleep(1 * time.Second)
}
}
}
stopCh := make(chan struct{})
go worker(stopCh)
time.Sleep(3 * time.Second)
close(stopCh) // 广播通知所有 worker 退出
适用场景:控制多个 goroutine 同时退出。
(2) 任务取消(类似 context)
func task(cancelCh <-chan struct{}) {
for i := 0; i < 5; i++ {
select {
case <-cancelCh:
fmt.Println("task cancelled")
return
default:
fmt.Println("task running:", i)
time.Sleep(1 * time.Second)
}
}
}
cancelCh := make(chan struct{})
go task(cancelCh)
time.Sleep(2 * time.Second)
close(cancelCh) // 广播取消任务
适用场景:提前终止长时间运行的任务。
(3) 资源清理
func cleanup(resourceCh <-chan string, doneCh chan<- struct{}) {
for resource := range resourceCh {
fmt.Println("cleaning up:", resource)
}
fmt.Println("all resources cleaned up")
close(doneCh)
}
resourceCh := make(chan string)
doneCh := make(chan struct{})
go cleanup(resourceCh, doneCh)
resourceCh <- "file1"
resourceCh <- "file2"
close(resourceCh) // 广播通知清理完成
<-doneCh // 等待清理完成
适用场景:确保所有资源被正确释放。
4. 注意事项
- 只有发送者应该关闭 channel,接收者关闭可能导致 panic。
- 重复关闭 channel 会导致 panic,需确保只关闭一次。
- 向已关闭的 channel 发送数据会 panic:
ch := make(chan int) close(ch) ch <- 1 // panic: send on closed channel nilchannel 的关闭也会 panic:var ch chan int close(ch) // panic: close of nil channel
5. 总结
| 特性 | 说明 |
|---|---|
| 广播通知 | close(ch) 会唤醒所有阻塞的接收者 |
| 零值返回 | 已关闭的 channel 会返回类型零值 |
ok=false |
v, ok := <-ch 可检测 channel 是否关闭 |
for-range 自动退出 |
适用于循环读取 channel |
select 检测关闭 |
可结合 select 处理关闭事件 |
Go 的 channel 关闭机制提供了一种高效的广播式通知方式,适用于并发控制、任务取消、资源清理等场景。正确使用该机制可以避免 goroutine 泄漏,并提高程序的健壮性。
Do not communicate by sharing memory; instead, share memory by communicating.

浙公网安备 33010602011771号