golang 中 channel cap设为1原理 | 有无缓冲的channel
在golang中,如果涉及消息传递或者是并发控制等,我们常常用到 channel,channel的具体原理这里不讨论,今天主要看看有无缓冲以及缓冲值的设计。
1.无缓冲的channel
联系 channel 的数据结构 mchan 可知,就没得 buf,但 sendq recvq 这些肯定都是有的,所以在无缓冲的 channel 中,如果写者写入 channel 而没有 读者来读取数据的话,这时的写入肯定是阻塞的,同样适用于只有读者没有写者的场景,只有当读者写者都具备的情况下才会正常读写,否则阻塞或者是形成死锁。
下面看几个例子。
同个goroutine中单读channel
func TestNoCacheChanOnlyRd(t *testing.T) {
inChan := make(chan int, 0)
<-inChan
}
同个goroutine中单写channel
func TestNoCacheChanOnlyWr(t *testing.T) {
inChan := make(chan int, 0)
inChan <- 1
}
同个goroutine中有写有读
func TestNoCacheChanWrAndRd(t *testing.T) {
inChan := make(chan int, 0)
inChan <- 1
<-inChan
}
不同goroutine中读写
func TestNoCacheChanWrAndRdInDiffGoroutine(t *testing.T) {
inChan := make(chan int)
go func() {
fmt.Println("写入到num:", 1)
inChan <- 1
close(inChan)
}()
go func() {
num := <-inChan
fmt.Println("读取到num:", num)
}()
time.Sleep(time.Second)
fmt.Println("主进程退出")
}
除了最后一个情况,其他情况全部都是要 deadLock 的,不信可以自己运行下。
2.有缓冲的channel
不同goroutine的读写
func TestCacheChanWrAndRd(t *testing.T) {
intChan := make(chan int, 1)
go func() {
for i := 0; i < 5; i++ {
intChan <- i
fmt.Println("写者写入:", i)
}
close(intChan)
}()
for num := range intChan {
fmt.Println("读者读到:", num)
}
fmt.Println("主进程退出")
}
因为有缓冲的存在,写者可以一直往里面写,读者在 range 遍历 channel 的时候,只要内部有数据,就可以读到数据,直到没有数据为止,如果 channel 关闭,range 也就退出了,这样就能很好实现有缓冲的 channel 的应用,这种不要求读者写者同时准备好,相当于一个阻塞队列,channel 有缓冲就可以写入,满了后面的写者就阻塞,但只要有读者就会一直读,除非 channel 没有了数据,读者也会陷入阻塞,当 channel 关闭了,则退出 channel 的遍历。
注意以下情况依然会有死锁问题。
有缓冲的 channel 只有写
func TestCacheChanOnlyWr(t *testing.T) {
intChan := make(chan int, 1)
intChan <- 1
}
有缓冲的 channel 只有读
func TestCacheChanOnlyRd(t *testing.T) {
intChan := make(chan int, 1)
<-intChan
}
总结
无缓冲的 channel 适合放在不同 goroutine 中完成读写或者数据传递等,有缓冲 channel 因为缓冲的存在,实际上就是一个阻塞队列,无缓冲 channel 的读写要求读者写者同时准备好,不然任何一方没准备好都会阻塞,有缓冲 channel 如果 cap 设为1,已经可以满足读者写者不用同时准备好,也不会陷入死锁(单写或者单读除外),而无缓冲的 channel 可能陷入死锁。
另外,有缓冲的 channel cap 设为多少根据业务场景来定,设为1是否可以满足需求,或者设为其他如1024 256 512等,根据业务来定。
以上是个人有限的认知,欢迎大神来喷。