文章目录
- GoLang之channel底层
GoLang之channel底层
注:本文以Go SDK v1.8进行讲解
1.channel类型介绍
2.接受命令
x := <- ch // 从ch中接收值并赋值给变量x
<-ch // 从ch中接收值,忽略结果
3.发送命令
ch <- 10 // 把10发送到ch中
4.关闭命令
close(ch)
5.channel异常
5.1异常介绍
5.2nil 接受
5.3nil 发送
5.4nil 关闭
5.5空 接受
5.5空 发送
5.6空 关闭
5.7满了 接受
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
var a int = <-ch
fmt.Println(a) //输出:1
}
5.8满了 发送
5.9满了 关闭
5.10make无缓冲 接受
5.11make无缓冲 发送
5.12make无缓冲 关闭
6.var创建
var 变量名称 chan 元素类型
var ch1 chan int // 声明一个传递整型的通道
var ch2 chan bool // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道
func main() {
var ch chan int
if ch == nil {
fmt.Println("ch是nil") //ch是nil
} else {
fmt.Println("ch不是nil")
}
fmt.Println(ch)//<nil>
}
7.make创建不指定缓冲区
7.1错误使用
func main() {
ch := make(chan int)
if ch == nil {
fmt.Println("ch是nil")
} else {
fmt.Println("ch不是nil") //ch不是nil
}
fmt.Println(ch) //0xc000018060
}
7.2无goroutine错误解决
7.3有goroutine错误解决
7.4有goroutine正确解决
8.make创建指定缓冲区
9.var结合make创建不指定缓冲区
make(chan 元素类型, [缓冲大小])
10.var结合make创建指定缓冲区
11.接受返回两个参数
12.close函数应用
13.hchan结构体
// hchan结构体位于runtime/chan.go文件
type hchan struct {
qcount uint // 当前队列中剩余元素个数,即chan 里元素数量
dataqsiz uint // chan 底层循环数组的长度,即可以存放的元素个数
buf unsafe.Pointer //指向底层循环数组的指针, 只针对有缓冲的 channel
elemsize uint16// chan 中每个元素的大小,用于在buf中定位元素位置
closed uint32// chan 是否被关闭的标志
elemtype *_type // chan 中元素类型,用于数据传递过程中的赋值;
// 队列下标,指示元素写入时存放到队列中的位置,已发送元素在循环数组中相对于底层数组的索引
sendx uint // send index
// 队列下标,指示元素从队列该位置读出,已接收元素在循环数组中相对于底层数组的索引
recvx uint // receive index
// 等待读消息的goroutine队列,等待接收的 goroutine 队列,表示被阻塞的 goroutine,这些 goroutine 由于尝试读取 channel里数据而被阻塞。
recvq waitq // list of recv waiters
// 等待写消息的goroutine队列,等待发送的 goroutine 队列,表示被阻塞的 goroutine,这些 goroutine 由于尝试向 channel 发送数据而被阻塞。
sendq waitq // list of send waiters
//互斥锁,保护 hchan 中所有字段,lock 用来保证每个读 channel 或写 channel 的操作都是原子的
//一个channel同时仅允许被一个goroutine读写
lock mutex
}
14.waitq双向链表
// waitq结构体位于runtime/chan.go文件
type waitq struct {
first *sudog
last *sudog
}
15.环形队列
16.等待队列
15.chantype结构体
//chantype位于runtime/type.go文件
type chantype struct {
typ _type
elem *_type
dir uintptr
}
16.makechan函数
// makechan函数位于runtime/chan.go文件
func makechan(t *chantype, size int) *hchan {
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")
}
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
//对于‘case mem == 0:’与‘case elem.ptrdata == 0:’只进行一次内存分配
switch {
//size 大小为 0(无缓冲类型)
case mem == 0:
// Queue or element size is zero.
c = (*hchan)(mallocgc(hchanSize, nil, true))
//1. 非缓冲型的,buf 没用,直接指向 chan 起始地址处
//2. 缓冲型的,能进入到这里,说明元素无指针且元素类型为 struct{},也无影响
c.buf = c.raceaddr()
//如果元素类型不含指针
case elem.ptrdata == 0:
// 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:
// 进行两次内存分配操作
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")
}
// 返回 hchan 指针
return c
}
//raceaddr方法位于runtime/chan.go文件
func (c *hchan) raceaddr() unsafe.Pointer {
return unsafe.Pointer(&c.buf)
}