企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 题目 对已经关闭的的 chan 进行读写,会怎么样?为什么? ## 回答 * 读已经关闭的 chan 能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。 * 如果 chan 关闭前,buffer 内有元素还未读 , 会正确读到 chan 内的值,且返回的第二个 bool 值(是否读成功)为 true。 * 如果 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个 bool 值一直为 false。 * 写已经关闭的 chan 会 panic ## 示例 ### 1\. 写已经关闭的 chan ~~~go func main(){ c := make(chan int,3) close(c) c <- 1 } //输出结果 panic: send on closed channel goroutine 1 [running] main.main() ... ~~~ * 注意这个 send on closed channel,待会会提到。 ### 2\. 读已经关闭的 chan ~~~go package main import "fmt" func main() { fmt.Println("以下是数值的chan") ci:=make(chan int,3) ci<-1 close(ci) num,ok := <- ci fmt.Printf("读chan的协程结束,num=%v, ok=%v\n",num,ok) num1,ok1 := <-ci fmt.Printf("再读chan的协程结束,num=%v, ok=%v\n",num1,ok1) num2,ok2 := <-ci fmt.Printf("再再读chan的协程结束,num=%v, ok=%v\n",num2,ok2) fmt.Println("以下是字符串chan") cs := make(chan string,3) cs <- "aaa" close(cs) str,ok := <- cs fmt.Printf("读chan的协程结束,str=%v, ok=%v\n",str,ok) str1,ok1 := <-cs fmt.Printf("再读chan的协程结束,str=%v, ok=%v\n",str1,ok1) str2,ok2 := <-cs fmt.Printf("再再读chan的协程结束,str=%v, ok=%v\n",str2,ok2) fmt.Println("以下是结构体chan") type MyStruct struct{ Name string } cstruct := make(chan MyStruct,3) cstruct <- MyStruct{Name: "haha"} close(cstruct) stru,ok := <- cstruct fmt.Printf("读chan的协程结束,stru=%v, ok=%v\n",stru,ok) stru1,ok1 := <-cs fmt.Printf("再读chan的协程结束,stru=%v, ok=%v\n",stru1,ok1) stru2,ok2 := <-cs fmt.Printf("再再读chan的协程结束,stru=%v, ok=%v\n",stru2,ok2) } ~~~ 输出结果 ~~~shell 以下是数值的chan 读chan的协程结束,num=1, ok=true 再读chan的协程结束,num=0, ok=false 再再读chan的协程结束,num=0, ok=false 以下是字符串chan 读chan的协程结束,str=aaa, ok=true 再读chan的协程结束,str=, ok=false 再再读chan的协程结束,str=, ok=false 以下是结构体chan 读chan的协程结束,stru={haha}, ok=true 再读chan的协程结束,stru=, ok=false 再再读chan的协程结束,stru=, ok=false ~~~ ## 多问一句 ### 1\. 为什么写已经关闭的`chan`就会`panic`呢? ~~~go //在 src/runtime/chan.go func chansend(c *hchan,ep unsafe.Pointer,block bool,callerpc uintptr) bool { //省略其他 if c.closed != 0 { unlock(&c.lock) panic(plainError("send on closed channel")) } //省略其他 } ~~~ * 当`c.closed != 0`则为通道关闭,此时执行写,源码提示直接`panic`,输出的内容就是上面提到的`"send on closed channel"`。 ### 2\. 为什么读已关闭的 chan 会一直能读到值? ~~~go func chanrecv(c *hchan,ep unsafe.Pointer,block bool) (selected,received bool) { //省略部分逻辑 lock(&c.lock) //当chan被关闭了,而且缓存为空时 //ep 是指 val,ok := <-c 里的val地址 if c.closed != 0 && c.qcount == 0 { if receenabled { raceacquire(c.raceaddr()) } unlock(&c.lock) //如果接受之的地址不空,那接收值将获得一个该值类型的零值 //typedmemclr 会根据类型清理响应的内存 //这就解释了上面代码为什么关闭的chan 会返回对应类型的零值 if ep != null { typedmemclr(c.elemtype,ep) } //返回两个参数 selected,received // 第二个采纳数就是 val,ok := <- c 里的 ok //也就解释了为什么读关闭的chan会一直返回false return true,false } } ~~~ * `c.closed != 0 && c.qcount == 0`指通道已经关闭,且缓存为空的情况下(已经读完了之前写到通道里的值) * 如果接收值的地址`ep`不为空 * 那接收值将获得是一个该类型的零值 * `typedmemclr`会根据类型清理相应地址的内存 * 这就解释了上面代码为什么关闭的 chan 会返回对应类型的零值