1.创建
带缓冲
ch := make(chan int,3)
不带缓冲
ch := make(chan int)
创建时会做一些检查:
- 元素大小不能超过64K
- 元素对齐大小不能超过maxAlign(8字节)
- 计算出来的内存是否超过限制
创建时的策略:
- 无缓冲的channel——会直接给hchan分配内存
- 有缓冲的channel并且元素不包含指针(buf指针,指向底层数组)——会为hchan和底层数组分配一段连续的地址
- 有缓冲的channel并且元素包含指针——会为hchan和底层数组分别分配地址
2.发送
包括检查和数据发送两个步骤
数据发送步骤
1.如果channel的读等待队列存在接收者goroutine(有发送者goroutine阻塞)
将数据直接发送给第一个等待的goroutine,唤醒接收的goroutine
2.如果channel的读等待队列不存在接收者goroutine(无有发送者goroutine阻塞)
如果buf指向的循环数组未满,会把数据发送到循环数组的队尾
如果buf指向的循环数组已满,就会阻塞,将当前goroutine加入写等待队列,并挂起等待唤醒
func chansend(c *hchan,ep unsafe.Pointer,block bool,callerpc uintptr)bool
阻塞式:
调用chansend函数,且block=true
ch <- 10
非阻塞式:
调用chansend函数,且block=false
通过select让其在将阻塞时直接返回
select {
case ch <- 10:
...
default
}
3.接收
包括检查和数据接收两个步骤
数据接收步骤
1.如果channel的写等待队列存在发送者goroutine(有发送者goroutine阻塞)
如果是无缓冲channel,直接从第一个发送者goroutine那里把数据拷贝给接收变量,唤醒发送的goroutine
如果是有缓冲channel(已满),将循环数组buf的队首元素拷贝给接收变量,将第一个发送者goroutine的数据拷贝到buf指向的循环数组队尾,唤醒发送的goroutine
2.如果channel的写等待队列不存在发送者goroutine(没有发送者goroutine阻塞)
如果buf指向的循环数组非空,将循环数组的队首元素拷贝给接收变量
如果buf指向的循环数组为空,这个时候就会阻塞,将当前goroutine加入读等待队列,并挂起等待唤醒
func chanrecv(c *hchan,ep unsafe.Pointer,block bool)(selected,received bool)
阻塞式:
调用chanrecv函数,且block=true
有4种方式
<-ch
v:= <-ch
v,ok := <-ch
//当channel关闭时,for循环会自动退出,无需主动监测channel是否关闭,可以防止读取已经关闭的channel,造成读到数据为通道所存储的数据类型的零值
for i := range ch {
fmt.Println(i)
}
非阻塞式:
调用chanrecv函数,且block=false
select {
case <- ch:
...
default
}
4.关闭
调用closechan函数
func closechan(c *hchan)
close(ch)