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)