@Golang亿点小细节之close()
你所忽略的,往往才是Bug的起源
close() 前置知识
1.不能去close()一个已经close()的channel
package main
func main(){
ch := make(chan int)
close(ch)
close(ch)
// output:
// panic: close of closed channel
}
2.channel被close()后,不可以写入(注意会panic:send on closed channel),但可以读取。读取规则是如果有缓存值则读缓存值,没有缓存值则读零。注意:v, ok := <-ch
中ok
并不能去判断channel在何时关闭
package main
import "fmt"
func main() {
ch := make(chan int,3)
for i := 0; i < 3; i++ {
ch <- i
}
close(ch)
for i := 0; i < 5; i++ {
v, ok := <-ch
fmt.Println(v, ok)
}
fmt.Println("如果没有下一行就是写入失败被阻塞了啊")
ch <- 1
fmt.Println("我是下一行")
/*
output:
0 true
1 true
2 true
0 false
0 false
如果没有下一行就是写入失败被阻塞了啊
panic: send on closed channel
*/
}
注意:v, ok := <-ch
中ok
并不一定能去判断channel在何时关闭
package main
import "fmt"
func main() {
ch := make(chan int, 3)
for i := 1; i < 4; i++ {
ch <- i
}
close(ch)
for i := 0; i < 4; i++ {
v, ok := <-ch
fmt.Println(v,ok)
}
/*
output
1 true
2 true
3 true
0 false
*/
}
为什么说使用ok
模式去判断channel是否关闭是不可靠的呢?
如上输出所示,在通道内存在缓存值时,即使channel被关闭了,ok
的值仍然是true
,要记得0
和false
才是一对哦!当channel被关闭且无缓存值时,ok
才是false
,同时使用channel时,最好在不需要使用的时候关闭,如果所有的goroutine
都被阻塞了,会panic
上菜
下面的代码是脑爆Go语言群友昙花逐月提供,他提出问题,三个协程都有close(),为何没有输出
panic: close of closed channel
,详情看注释哈
package main
import (
"fmt"
)
func main() {
a := 0
fn := func() int {
a++
return a
}
ch := make(chan int, 1)
chh := make(chan int, 3)
for i := 0; i < 3; i++ {
go func(j int) {
for {
ch <- 1
n := fn()
if n > 100 {
// 当第一个协程进入这段代码区域时,chh最终会被关闭
// 剩下的两个协程因为chh被关闭,在写入按理来说应该会报panic: send on closed channel
// 然而并未出现,原因在于其实协程在ch <- 1 的位置被阻塞了,为何?
// ch容量为1,在第一个协程退出的时候并为将ch中的值取出,导致被阻塞
chh <- 1
close(chh)
return
}
fmt.Println("go", j, n)
<-ch
}
}(i)
}
for i := 0; i < 3; i++ {
// 那这样是不是代表这chh中只有一个值,是的!但是为何主进程没阻塞呢?
// 原因在于channle被关闭能读出0,所以这个循环在chh被关闭后马上就结束了
<-chh
}
}
饭后甜点
昙花逐月在解决上面的问题后,还提出一个问题,怎么利用channel来实现安全退出?,事实上上面的方案就是一个雏形,只是需要稍微修改一下代码,为了方便阅读,我在上述代码的基本盘不变的情况下重新组织一下代码。
func main() {
data := make(chan int, 100) // 用来存储0-100
code := make(chan int, 1) // 相当于ch,写入code则有执行权
count := make(chan int, 3) // 相当于chh,用于统计
for i := 0; i < 3; i++ {
go func(code chan int, data <-chan int, count chan<- int) {
for {
code <- 1 // 获得执行权
if v, ok := <-data; ok {
fmt.Println(v)
<-code //释放执行权
} else {
count <- 1 // 计数
<- code //释放执行权
return
}
}
}(code, data, count)
}
go func(data chan<- int) {
for i := 0; i < 101; i++ {
data <- i
}
close(data)
}(data)
for i := 0; i < 3; {
i = i + <-count // 统计
}
}
事实上这种实现和sync.WaitGroup有异曲同工之妙