channel(通道)用于goroutine(协程)之间的通信。它提供了一种在不同协程之间传递数据的机制。channel是一种类型安全的、阻塞的、先进先出(FIFO)的数据结构,确保发送的数据按照发送的顺序接收。Go语言提供通过通信来共享内存,而不是通过共享内存来通信
1. 基本数据结构
channel的底层源码和相关实现在src/runtime/chan.go中
hchan是Channel底层数据结构对应的结构体
对应的字段说明如下:
qcount : 循环数组中的元素数量,长度
dataqsiz :循环数组的大小,容量
buf :指向底层循环数组的指针(环形缓冲区)
elemsize :
closed:是否关闭的标志,0:未关闭,1:已关闭
elemtype *_type : channel中的元素类型
sendx: 下一次写的位置
recvx :下一次读的位置
recvq:读等待队列
sendq:等待队列
lock mutex:互斥锁,保证读写channel时的并发安全问题
2. Channel的创建
2.1Channel语法
2.2Channel的基本用法
2.3创建源码分析
func makechan(t *chantype, size int) *hchan {
//获取无素类型
elem := t.elem
// 元素的大小必须小于64K
// 编译器已经检查了这一点,但是为了安全起见再次进行检查
if elem.size >= 1<<16 {
throw("makechan: invalid channel element type")
}
if hchanSize%maxAlign != 0 || elem.align > maxAlign {
throw("makechan: bad alignment")
}
// 计算所需要的内存大小
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:只分配hchan的内存
c = (*hchan)(mallocgc(hchanSize, nil, true))
// Race detector uses this location for synchronization.
c.buf = c.raceaddr()
case elem.ptrdata == 0:
// 元素不包含指针
// 一次性为hchan和buf分配连续的内存
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
default:
// 元素包含指针:分别分配hchan结构体和底层循环数组的内存
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
// 设置hchan中的elemsize、elemtype、dataqsiz、lock等属性
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
}
3.Channel发送数据底层原理
3.1发送的情形
3.2直接发送
3.2.1直接发送的原理
3.2.2直接发送实现
3.2.3直接发送源码分析
源码位置:runtime/chan.go 的channelsend
3.3放入缓存
3.3.1放入缓存原理
3.3.2放入缓存实现
3.3.3源码分析
3.4休眠等待
3.4.1休眠等待原理
3.4.2休眠等待实现
3.4.3源码分析
4.Channel接收数据底层原理
4.1channel接收的情况
4.2同步接收
4.2.1同步接收的原理
4.2.2同步接收的实现
4.2.3同步接收源码分析
源码位置runtime/chan.go