golang中向一个closed的channel中发送数据,会造成严重“事故”。也是容易犯的常见毛病。
- 错误示范:
package main
import (
"fmt"
"sync"
"time"
)
var mu sync.Mutex
var isChanClosed = false
func setClosedFlag() {
mu.Lock()
isChanClosed = true
mu.Unlock()
}
func getClosedFlag() bool {
mu.Lock()
defer mu.Unlock()
return isChanClosed
}
func main() {
ch := make(chan int, 10)
//write
go func() {
for i := 0; i < 20; i++ {
flag := getClosedFlag()
if flag == true {
return
}
fmt.Println(" before write isChanClosed:", flag)
ch <- i
time.Sleep(time.Millisecond * 100)
}
fmt.Println(" not write.....")
}()
//read
go func() {
for {
time.Sleep(time.Millisecond * 500)
m, ok := <-ch
if ok {
fmt.Println("channel not closed m=", m)
} else {
fmt.Println("channel closed, left elem's len:", len(ch))
for m := range ch {
fmt.Println(" left: %d", m)
}
}
}
}()
time.Sleep(5 * time.Second)
setClosedFlag()
close(ch)
fmt.Println(">>>>> closed channel, isChanClosed:", getClosedFlag())
time.Sleep(time.Second * 300)
}
运行后:

根据flag 判断条件,write 之前是false 没有关闭, write时 channel 关闭了。报错。感觉 根据flag来判断还是“慢“了点。
一种解决方案: 使用select ,并将write 功能 放在 default 分支中。
func main() {
ch := make(chan int, 10)
stop := make(chan bool)
//write
go func() {
for {
select {
case <-stop:
fmt.Println("ch closed. stop receive data")
return
default:
for i := 0; i < 20; i++ {
flag := getClosedFlag()
if flag == true {
return
}
fmt.Println(" before write isChanClosed:", flag)
ch <- i
time.Sleep(time.Millisecond * 100)
}
fmt.Println(" not write.....")
}
}
}()
//read
go func() {
for {
time.Sleep(time.Millisecond * 500)
m, ok := <-ch
if ok {
fmt.Println("channel not closed m=", m)
} else {
fmt.Println("channel closed, left elem's len:", len(ch))
for m := range ch {
fmt.Println(" left: %d", m)
}
}
}
}()
time.Sleep(5 * time.Second)
stop <- true
close(ch)
fmt.Println(">>>>> closed channel, isChanClosed:", getClosedFlag())
time.Sleep(time.Second * 300)
}
运行结果:

本文讨论了在Go语言中向已关闭的channel发送数据可能导致的问题,并提供了一个使用select和额外stop通道的解决方案,以更优雅地停止写入操作。通过在default分支中处理写入,当接收到停止信号时,可以立即停止,避免了错误的发生。

被折叠的 条评论
为什么被折叠?



