channel
先看一下源码中的说明
At least one of c.sendq and c.recvq is empty, except for the case of an unbuffered channel with a single goroutine blocked on it for both sending and receiving using a select statement, in which case the length of c.sendq and c.recvq is limited only by the size of the select statement.
For buffered channels, also:
c.qcount > 0 implies that c.recvq is empty.
c.qcount < c.dataqsiz implies that c.sendq is empty.
c.sendq和c.recvq至少有一个是empty;有一种情况例外:对于一个无缓冲的chan,在select语句中使用chan收发数据会被single gouroutine阻塞,[演示示例如下],此时c.sendq和c.recv.q的长度由select的size的决定;对于有缓冲的chan:
1.c.qcount>0,表明c.recvq为empty
2.c.qcount<c.dataqsiz表明sendq为empty
- 对于"unbuffered chan",放在select语句中会被阻塞
- 放在一个goroutine中会产生"dead lock"
- 放在不同的协程中就不会产生dead lock
- 对于nil chan放在同一个协程中和不同的协程中都会被阻塞产生deadlock;放在select语句可以走default分支
- nil chan只是声明而没有定义,没有为其分配内存;empty chan为其分配内存但是无缓冲
hchan
接下来看一下chan结构体"hchan"的定义
type hchan struct {
qcount uint // buf中的元素个数
dataqsiz uint // buf的容量
buf unsafe.Pointer // 指向存储数据的底层数组
elemsize uint16 //元素大小
closed uint32 //标识chan是否关闭
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
//Lock保护hchan中的所有字段,以及在此通道上阻塞的sudogs中的几个字段。
lock mutex
}
type waitq struct {
first *sudog
//sudog represents a g in a wait list,
//such as for sending/receiving on a channel.
last *sudog
}
//只列出了部分字段
type sudog struct {
g *g
next *sudog
prev *sudog
......
......
c *hchan // channel
}
makechan
利用"make(chan int,1)“创建并初始化一个chan,会调用"makechan”
func makechan(t *chantype, size int) *hchan {
//获取chan中存储元素的相关信息
elem := t.elem
// compiler checks this but be safe.
if elem.size >= 1<<16 {
throw("makechan: invalid channel element type")
}
if hchanSize%maxAlign != 0 || elem.align > maxAlign {
throw("makechan: bad alignment")
}
//elem.size表示元素的大小,size表示元素的个数
mem, overflow := math.MulUintptr(elem.size, uintptr(size))
if overflow || mem > maxAlloc-hchanSize || size < 0 {
panic(plainError("makechan: size out of range"))
}
var c *hchan
switch {
case mem == 0:
//元素大小为0或者元素个数为0
// Queue or element size is zero.
c = (*hchan)(mallocgc(hchanSize, nil, true))
// Race detector uses this location for synchronization.
c.buf = c.raceaddr()
case elem.ptrdata == 0:
//元素不包含指针类型的数据,一次性分配hchan和buf
// Elements do not contain pointers.
// Allocate hchan and buf in one call.
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
default:
//元素含有指针,需要先为hchan分配内存,然后再为buf分配内存,这样做是为了方便gc回收
// Elements contain pointers.
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)
lockInit(&c.lock, lockRankHchan)
if debugChan {
print("makechan: chan=", c, "; elemsize=", elem.size, "; dataqsiz=", size, "\n")
}
return c
}
总结一下分配的方式:
- 对于无缓冲的chan [size==0]或者chan中存储的元素大小为0[elemsize==0]只分配hchan结构体大小的内存
- 不含有指针类型的数据,一次性分配hchan+bufsize大小的内存
- 含有指针类型的数据,先为hchan结构体分配内存,然后再为buf分配内存
chansend
接下来看一下,向chan中发送数据的流程
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
if c == nil {
if !block {
return false
}
//当前的协程因向nilchan发送send而被挂起阻塞
gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
throw("unreachable")
}
if debugChan {
print("chansend: chan=", c, "\n")
}
if raceenabled {
racereadpc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(chansend))
}
/*
fast path:
在不获取锁的情况下,检查失败的非阻塞操作
full(c)为true的情况:
1.无缓冲但是没有等待接收的reciver
2.有缓冲但是缓冲通道满了
*/
if !block && c.closed == 0 && full(c) {
return false
}
var t0 int64
if blockprofilerate > 0 {
t0 = cputicks()
}
//上锁
lock(&c.lock)
if c.closed != 0 {
//chan已经被关闭解锁,并报panic
unlock(&c.lock)
panic(plainError("send on closed channel"))
}
//c.recvq.dequeue获取recvq的第一个sudog