无缓冲通道
是指在接收前没有能力保存任何值得通道。
这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接收操作的goroutine阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的,其中任意一个操作都无法离开另一个操作单独存在。
上图所示,如同接力赛。根据图编号观察①两个协程,创建好了通道②一个往通道里放,这时候两边阻塞④这时候另一个协程要接⑤另一个协程取出来,从①-⑤都是阻塞的,⑥才完成交接,才不会阻塞。
再比喻: 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。
无缓冲channel创建
ch := make(chan int, 0) //第二个参数为0,或者不写第二个参数
如果没有指定缓冲区容量,那么该通道就是同步的,因此会阻塞到发送者准备好发送和接收者准备好接收。
代码案例
package main
import (
"fmt"
"time"
)
func main() {
//创建一个无缓存的channel
ch := make(chan int, 0)
//len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小,两者这里永远都是0
fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
//新建协程
go func() {
for i := 0; i < 3; i++ { //写三次
fmt.Printf("子协程:i = %d\n", i)
ch <- i //往chan写内容
fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
}
}()
//延时2秒
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ { //必须读三次
num := <-ch //读管道中内容,没有内容前,阻塞
fmt.Println("num = ", num)
}
}
len(ch) = 0, cap(ch)= 0
子协程:i = 0
num = 0
len(ch) = 0, cap(ch)= 0
子协程:i = 1
len(ch) = 0, cap(ch)= 0
子协程:i = 2
num = 1
num = 2
流程分析
主协程执行到这里,阻塞两秒
//延时2秒
time.Sleep(2 * time.Second)
//两秒钟时间子协程肯定把for循环执行完毕,但这里也会出现阻塞
//阻塞原因是:当执行到往通道写数据是无缓冲的,对方不读之前会阻塞。也就是,在主协程等着子协程写完,但是主协程还没到读的时候,这时候出现阻塞,等到主协程读完数据才会往下走。
可以执行观察一下程序的执行卡顿观察阻塞
for i := 0; i < 3; i++ { //写三次
fmt.Printf("子协程:i = %d\n", i)
ch <- i //往chan写内容
fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
}
打印结果分析:首先子协程for循环往管道里写入一个数据,紧接着主协程for循环出现阻塞,然后主协程for循环从管道读数据,读完了打印。主协程打印完,子协程for循环继续给管道数据,但也有可能主协程读完数据没来得及打印,子协程就把数据写入管道并打印完毕,因为两个是同时并行的。
有缓冲通道
指通道可以保存多个值。
如果给定了一个缓冲区容量,那么通道就是异步的,只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行
上图所示:
①右侧的goroutine正在从通道接收一个值。
②右侧的goroutine独立完成了接手值得动作,而左侧的goroutine正在发送一个新值到通道里。
③左侧的goroutine还在向通道发送新值,而右侧的goroutine正在从通道接收另一个值。这个步骤里的两个操作既不是同步,也不会互相阻塞。
④所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存储更多的值
有缓冲channel创建
ch := make(chan int, 3) //容量是3
代码案例
package main
import (
"fmt"
"time"
)
func main() {
//创建一个有缓存的channel
ch := make(chan int, 3) //容量是3
//len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小
fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
//新建协程
go func() {
for i := 0; i < 10; i++ { //这里数据量大于管道容量,会出阻塞
ch <- i //往chan写内容,如果主协程没读的话,写满3个就会阻塞在此
fmt.Printf("子协程[%d]: len(ch) = %d, cap(ch)= %d\n", i, len(ch), cap(ch))
}
}()
//延时
time.Sleep(2 * time.Second)
for i := 0; i < 10; i++ { //这里数据量大于管道容量,会出阻塞
num := <-ch //读管道中内容,没有内容前,阻塞
fmt.Println("num = ", num)
}
}
len(ch) = 0, cap(ch)= 3
子协程[0]: len(ch) = 1, cap(ch)= 3
子协程[1]: len(ch) = 2, cap(ch)= 3
子协程[2]: len(ch) = 3, cap(ch)= 3
num = 0
num = 1
num = 2
num = 3
子协程[3]: len(ch) = 3, cap(ch)= 3
子协程[4]: len(ch) = 0, cap(ch)= 3
子协程[5]: len(ch) = 1, cap(ch)= 3
子协程[6]: len(ch) = 2, cap(ch)= 3
子协程[7]: len(ch) = 3, cap(ch)= 3
num = 4
num = 5
num = 6
num = 7
num = 8
子协程[8]: len(ch) = 3, cap(ch)= 3
子协程[9]: len(ch) = 0, cap(ch)= 3
num = 9
总结一下有缓冲channel和无缓冲channel的特点与不同
无缓冲的与有缓冲channel有着重大差别,那就是一个是同步的 一个是非同步的。
比如
c1:=make(chan int) 无缓冲
c2:=make(chan int,1) 有缓冲
c1<-1
无缓冲: 不仅仅是向 c1 通道放 1,而是一直要等有别的携程 <-c1 接手了这个参数,那么c1<-1才会继续下去,要不然就一直阻塞着。
有缓冲: c2<-1 则不会阻塞,因为缓冲大小是1(其实是缓冲大小为0),只有当放第二个值的时候,第一个还没被人拿走,这时候才会阻塞。