Skip to content

Latest commit

 

History

History
152 lines (122 loc) · 4.55 KB

q018.md

File metadata and controls

152 lines (122 loc) · 4.55 KB

对已经关闭的的chan进行读写,会怎么样?为什么?

题目

对已经关闭的的 chan 进行读写,会怎么样?为什么?

回答

  • 读已经关闭的 chan 能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。
    • 如果 chan 关闭前,buffer 内有元素还未读 , 会正确读到 chan 内的值,且返回的第二个 bool 值(是否读成功)为 true。
    • 如果 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个 bool 值一直为 false。
  • 写已经关闭的 chan 会 panic

示例

1. 写已经关闭的 chan

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

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)
}

输出结果

以下是数值的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 呢?

//在 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 会一直能读到值?

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 会返回对应类型的零值