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)
}
运行结果: