大纲
c <- x
, <-c
这样的语法可能都记不清晰,怎么办?本文教你从源码编译器的角度全方位的剖析 channel 的用法。
本质上就实现角度来讲,golang 的 channel 就是一个环形队列(ringbuffer)的实现。我们称 chan 为管理结构,channel 里面可以放任何类型的对象,我们称之为元素。
我们从 channel 的使用姿势入手,讲解最详细的 channel 使用方法。
我们从宏观的 chan 使用姿势入手,总结来讲,有以下几种姿势:
创建一个 channel ,一般用户使用姿势有两种,分别是创建有 buffer 和没有 buffer 的 channel 。
// no buffer 的 channel
c := make(chan int)
// 自带 buffer 的 channel
c1 := make(chan int , 10)
这个对应了实际函数是 makechan
,位于 runtime/chan.go
文件里。
用户使用姿势:
c <- x
对应函数实现 chansend
,位于 runtime/chan.go
文件。
用户使用姿势:
v := <-c
v, ok := <-c
对应函数分别是 chanrecv1
和 chanrecv2
,位于 runtime/chan.go
文件。
用户使用姿势:
select {
case c <- v:
// ... foo
default:
// ... bar
}
对应函数实现为 selectnbsend
, 位于 runtime/chan.go
文件中。
用户使用姿势:
select {
case v = <-c:
// ... foo
default:
// ... bar
}
对应函数实现为 selectnbrecv
, 位于 runtime/chan.go
文件中。
用户使用姿势:
select {
case v, ok = <-c:
// ... foo
default:
// ... bar
}
对应函数实现为 selectnbrecv2
, 位于 runtime/chan.go
文件中。
用户使用姿势:
for m := range c {
// ... do something
}
对应使用函数 chanrecv2
,位于 runtime/chan.go
文件中。
上面我们通过宏观的用户使用姿势,了解清楚了不同的使用姿势对应了不同实现函数(这个翻译是编译器来做的),我们接下来就是仔细看下这些函数的实现。
makechan
负责 channel 的创建,当我们 go 程序里写类似 v := make(chan int)
的初始化语句,就会相应的调用不同类型对应的初始化函数,其中 channel 的初始化函数就是 makechen
。
runtime.makechan
定义原型:
func makechan(t *chantype, size int) *hchan {
}
通过这个,我们能得知到,声明创建一个 channel ,本质上是得到了一个 hchan 的指针,所以 channel 的核心结构就是基于 hchan 来实现的。
其中 t 参数是指明元素类型:
type chantype struct {
typ _type
elem *_type
dir uintptr
}
size 指明这个 channel buffer 槽位有多少。如果是带 buffer 的 channel,比如那么 size 就是槽位数,如果没有指定,那么就是 0;
// size == 0
a := make(chan int)
// size == 2
b := make(chan int, 2)
我们看下 makechan 做的事情,其实很简单,就只做了两件事:
func makechan(t *chantype, size int) *hchan {
// 参数校验
// 初始化 hchan 结构
}
参数校验无非就是一些越界,或者 limit 的校验。
初始化 hchan 则简单的分为三种情况:
switch {
// no buffer 的场景,这种 channel 可以看成 pipe;
case mem == 0:
c = (*hchan)(mallocgc(hchanSize, nil, true))
c.buf = c.raceaddr()
// channel 元素不含指针的场景,那么是分配出一个大内存块;
case elem.ptrdata == 0:
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
// 默认场景,hchan 结构体和 buffer 内存块单独分配;
default:
c = new(hchan)
c.buf = mallocgc(mem, elem, true)