文章目录
前言
goroutine、channel以及GMP模型是学习golang绕不开的部分,之前学习golang的时候对这一块的理解不够深入,本文将深度分析并且总结他们的底层原理。
一、channel的底层原理
channel又称为管道,用于数据传递或数据共享,其本质是一个先进先出的队列,使用goroutine+channel进行数据通讯简单高效,同时也线程安全,多个goroutine可同时修改一个channel,不需要加锁。CSP(Communicating Sequential Process)并发模型,就是通过 goroutine 和 channel 来实现的)
channel有哪些状态:
**未初始化的状态,**只进行了声明,或者手动赋值为nil。
**active:**正常的channel,可读或者可写。
closed:已关闭,channel的值不是nil,关闭的状态的channel仍然可以读值(取值),但不能写值(会报panic: send on closed channel),nil状态的channel是不能close(panic: close of nil channel)的。如果关闭后的 channel 没有数据可读取时,将得到零值,即对应类型的默认值。
1、底层数据结构
通过var声明或者make函数创建的channel变量是一个存储在函数栈帧上的指针,占用8个字节,指向堆上的hchan结构体
buf指向一个底层的循环数组,只有设置为有缓存的channel才会有buf
sendx和recvx分别指向底层循环数组的发送和接收元素位置的索引
sendq和recvq分别表示发送数据的被阻塞的goroutine和读取数据的goroutine,这两个都是一个双向链表结构
sendq和recvq 的结构为等待队列类型,sudog是对goroutine的一种封装 是双向链表,包含一个头结点和一个尾结点,每个节点是一个sudog结构体变量,记录哪个协程在等待,等待的是哪个channel,等待发送/接收的数据在哪里
type hchan struct {
qcount uint // channel中的元素个数
dataqsiz uint // channel中循环队列的长度
buf unsafe.Pointer // channel缓冲区数据指针
elemsize uint16 // buffer中每个元素的大小
closed uint32 // channel是否已经关闭,0未关闭
elemtype *_type // channel中的元素的类型
sendx uint // channel发送操作处理到的位置
recvx uint // channel接收操作处理到的位置
recvq waitq //读等待队列 等待接收的sudog(sudog为封装了goroutine和数据的结构)队列由于缓冲区空间不足而阻塞的goroutine列表
sendq waitq //写等待队列 等待发送的sudog队列,由于缓冲区空间不足而阻塞的goroutine列表
lock mutex // 一个轻量级锁
}
type waitq struct {
first *sudog
last *sudog
}
type sudog struct {
g *g
next *sudog
prev *sudog
elem unsafe.Pointer
c *hchan
...
}
2、创建关闭
创建channel 有两种,一种是带缓冲的channel,一种是不带缓冲的channel
// 带缓冲
ch := make(chan int, 3)
// 不带缓冲
ch := make(chan int)
创建时的策略:
如果是无缓冲的 channel,会直接给 hchan 分配内存
如果是有缓冲的 channel,并且元素不包含指针,那么会为 hchan 和底层数组分配一段连续的地址
如果是有缓冲的 channel,并且元素包含指针,那么会为 hchan 和底层数组分别分配地址
关闭
3、发送接受
如果 channel 的读等待队列存在接收者goroutin