Golang入门——channel的进阶使用
单向管道
顾名思义,单向管道就是只能用于发送或者接收数据。从管道的数据结构上看,并没有单向的管道。所谓的单向管道就是对管道的一种限制。
func readChannel(ChanName <-chan int){ ... }
func writeChannel(ChanName chan<- int){ ... }
其中 <-chan 和 chan <- 均为单向管道。如果向读管道写入数据或 者从写管道读取数据时,则会在编译的时候就会报错。
关闭管道
使用 close(ChannelName) 对管道进行关闭。此时会将接收队列中的所有协程结束等待状态,同时给予其nil值。而发送队列中的所有协程也会结束等待状态,但是会产生panic。
此外,关闭关闭的管道,关闭值为nil的管道以及向已经关闭的管道写入数据都会产生panic。
有缓冲区管道与无缓冲区管道的区别
声明管道
make(chan type)//无缓冲区
make(chan type,size)//有缓冲区
首先先要说向管道读写操作的流程。
操作 | 说明 |
---|---|
有缓冲区写操作 | 发送端首先检查接收队列,如果接收队列不为空,表示buffer中没有数据,发送端可直接取接收队列中的一个协程,将数据写给该协程并将其唤醒。如果接收队列为空,则需要判断buffer中是否已满,如果已满的话,该发送端则进入发送队列,进入等待状态。未满则直接将数据写入buffer中,结束写的操作。 |
无缓冲区写操作 | 由于没有缓冲区发送端直接检查接收队列,如果接收队列为空,则将进入发送队列中,进入等待状态。如果接收队列不为空,则直接将数据写给接收队列中的协程,并将其唤醒。 |
有缓冲区读操作 | 接收端首先检查发送队列,如果发送队列为空,则检查buffer。如果buffer为空,则该协程进入接收队列,并陷入等待状态。如果buffer中有数据,则读取buffer的首部数据。如果发送队列不为空,则读取buffer队列中的首部的数据,并将接发送队列中的首个协程的数据写到buffer的尾部,该进程被唤醒。 |
无缓冲区读操作 | 接收端检查发送队列,如果发送队列为空,则将该协程添加至等待队列尾部。如果发送队列不为空,则取发送队列的队首数据,并使其退出等待状态。 |
由此可见无缓冲区的情况是有缓冲区的变种情况。
那么有无缓冲区到底有什么实质的影响呢?看一下的代码可能就会发现其中的问题。
func main(){
ch := make(chan int)//无缓冲区
tag := sync.WaitGroup{}
tag.Add(1)
go func(){
ch <- 1
fmt.Println("Send Finish")
tag.Done()
}()
tag.Wait()
fmt.Println(<-ch)
}
func main(){
ch := make(chan int,1) //有缓冲区
tag := sync.WaitGroup{}
tag.Add(1)
go func(){
ch <- 1
fmt.Println("Send Finish")
tag.Done()
}()
tag.Wait()
fmt.Println(<-ch)
}
通过运行结果,可以发现无缓冲区的程序产生死锁。原因为无缓冲区时,写操作需要等待读操作使其退出等待状态,但是在该程序中,由于该协程需要等待一个读操作而主进程又在等待该协程的结束,导致死锁。这时应使用有缓冲区的管道解决该死锁的问题。