Go底层channel实现原理及示例详解
怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《Go底层channel实现原理及示例详解》,涉及到channel、底层原理,有需要的可以收藏一下
概念:
Go中的channel 是一个队列,遵循先进先出的原则,负责协程之间的通信(Go 语言提倡不要通过共享内存来通信,而要通过通信来实现内存共享,CSP(Communicating Sequential Process)并发模型,就是通过 goroutine 和 channel 来实现的)
使用场景:
停止信号监听
定时任务
生产方和消费方解耦
控制并发数
底层数据结构:
通过var声明或者make函数创建的channel变量是一个存储在函数栈帧上的指针,占用8个字节,指向堆上的hchan结构体
源码包中src/runtime/chan.go定义了hchan的数据结构:

hchan结构体:
type hchan struct {
closed uint32 // channel是否关闭的标志
elemtype *_type // channel中的元素类型
// channel分为无缓冲和有缓冲两种。
// 对于有缓冲的channel存储数据,使用了 ring buffer(环形缓冲区) 来缓存写入的数据,本质是循环数组
// 为啥是循环数组?普通数组不行吗,普通数组容量固定更适合指定的空间,弹出元素时,普通数组需要全部都前移
// 当下标超过数组容量后会回到第一个位置,所以需要有两个字段记录当前读和写的下标位置
buf unsafe.Pointer // 指向底层循环数组的指针(环形缓冲区)
qcount uint // 循环数组中的元素数量
dataqsiz uint // 循环数组的长度
elemsize uint16 // 元素的大小
sendx uint // 下一次写下标的位置
recvx uint // 下一次读下标的位置
// 尝试读取channel或向channel写入数据而被阻塞的goroutine
recvq waitq // 读等待队列
sendq waitq // 写等待队列
lock mutex //互斥锁,保证读写channel时不存在并发竞争问题
}
等待队列:
双向链表,包含一个头结点和一个尾结点
每个节点是一个sudog结构体变量,记录哪个协程在等待,等待的是哪个channel,等待发送/接收的数据在哪里
type waitq struct {
first *sudog
last *sudog
}
type sudog struct {
g *g
next *sudog
prev *sudog
elem unsafe.Pointer
c *hchan
...
}
操作:
创建
使用 make(chan T, cap) 来创建 channel,make 语法会在编译时,转换为 makechan64 和 makechan
func makechan64(t *chantype, size int64) *hchan {
if int64(int(size)) != size {
panic(plainError("makechan: size out of range"))
}
return makechan(t, int(size))
}
创建channel 有两种,一种是带缓冲的channel,一种是不带缓冲的channel
// 带缓冲 ch := make(chan int, 3) // 不带缓冲 ch := make(chan int)
创建时会做一些检查:
- 元素大小不能超过 64K
- 元素的对齐大小不能超过 maxAlign 也就是 8 字节
- 计算出来的内存是否超过限制
创建时的策略:
- 如果是无缓冲的 channel,会直接给 hchan 分配内存
- 如果是有缓冲的 channel,并且元素不包含指针,那么会为 hchan 和底层数组分配一段连续的地址
- 如果是有缓冲的 channel,并且元素包含指针,那么会为 hchan 和底层数组分别分配地址
发送
发送操作,编译时转换为runtime.chansend函数
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool
阻塞式:
调用chansend函数,并且block=true
ch非阻塞式:
调用chansend函数,并且block=false
select { case ch向 channel 中发送数据时大概分为两大块:检查和数据发送,数据发送流程如下:
如果 channel 的读等待队列存在接收者goroutine
- 将数据直接发送给第一个等待的 goroutine, 唤醒接收的 goroutine
如果 channel 的读等待队列不存在接收者goroutine
- 如果循环数组buf未满,那么将会把数据发送到循环数组buf的队尾
- 如果循环数组buf已满,这个时候就会走阻塞发送的流程,将当前 goroutine 加入写等待队列,并挂起等待唤醒
接收
发送操作,编译时转换为runtime.chanrecv函数
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
阻塞式:
调用chanrecv函数,并且block=true
非阻塞式:
调用chanrecv函数,并且block=false
select { case向 channel 中接收数据时大概分为两大块,检查和数据发送,而数据接收流程如下:
如果 channel 的写等待队列存在发送者goroutine
- 如果是无缓冲 channel,直接从第一个发送者goroutine那里把数据拷贝给接收变量,唤醒发送的 goroutine
- 如果是有缓冲 channel(已满),将循环数组buf的队首元素拷贝给接收变量,将第一个发送者goroutine的数据拷贝到 buf循环数组队尾,唤醒发送的 goroutine
如果 channel 的写等待队列不存在发送者goroutine
- 如果循环数组buf非空,将循环数组buf的队首元素拷贝给接收变量
- 如果循环数组buf为空,这个时候就会走阻塞接收的流程,将当前 goroutine 加入读等待队列,并挂起等待唤醒
关闭
关闭操作,调用close函数,编译时转换为
runtime.closechan函数close(ch)func closechan(c *hchan)案例分析:
package main import ( "fmt" "time" "unsafe" ) func main() { // ch是长度为4的带缓冲的channel // 初始hchan结构体重的buf为空,sendx和recvx均为0 ch := make(chan string, 4) fmt.Println(ch, unsafe.Sizeof(ch)) go sendTask(ch) go receiveTask(ch) time.Sleep(1 * time.Second) } // G1是发送者 // 当G1向ch里发送数据时,首先会对buf加锁,然后将task存储的数据copy到buf中,然后sendx++,然后释放对buf的锁 func sendTask(ch chan string) { taskList := []string{"this", "is", "a", "demo"} for _, task := range taskList { ch总结hchan结构体的主要组成部分有四个:
- 用来保存goroutine之间传递数据的循环数组:buf
- 用来记录此循环数组当前发送或接收数据的下标值:sendx和recvx
- 用于保存向该chan发送和从该chan接收数据被阻塞的goroutine队列: sendq 和 recvq
- 保证channel写入和读取数据时线程安全的锁:lock
今天带大家了解了channel、底层原理的相关知识,希望对你有所帮助;关于Golang的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
浅谈Golang内存逃逸
- 上一篇
- 浅谈Golang内存逃逸
- 下一篇
- Golang交叉编译(跨平台编译)的使用
-
- 玩命的台灯
- 这篇技术贴真是及时雨啊,太详细了,真优秀,mark,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2022-12-28 10:19:14
-
- 彩色的月饼
- 真优秀,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢楼主分享文章!
- 2022-12-28 02:22:25
-
- Golang · Go教程 | 22小时前 |
- Go go.sum 校验记录的提交策略
- 160浏览 收藏
-
- Golang · Go教程 | 22小时前 |
- Go mod graph 排查依赖引入路径的方法
- 142浏览 收藏
-
- Golang · Go教程 | 22小时前 |
- Go coverprofile 按包查看覆盖盲区
- 262浏览 收藏
-
- Golang · Go教程 | 22小时前 |
- Go t.Setenv 测试 os.UserHomeDir 的环境读取边界
- 231浏览 收藏
-
- Golang · Go教程 | 22小时前 | Go教程 · Go encoding/json io.Reader buffered json.Decoder JSON流
- Go json.Decoder.Buffered 怎样读取尚未消费的输入
- 484浏览 收藏
-
- Golang · Go教程 | 22小时前 | goroutine · 流式处理 · Go教程 · Go io.Pipe 生产者消费者 背压 PipeWriter PipeReader
- Go io.Pipe 串流生产者消费者的背压处理
- 228浏览 收藏
-
- Golang · Go教程 | 22小时前 |
- Go 并发读写 map 的保护结构
- 198浏览 收藏
-
- Golang · Go教程 | 22小时前 |
- Go 接口中携带 nil 指针的判断方法
- 380浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 500次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 558次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 586次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 537次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 349次使用
-
- Golang底层原理解析String使用实例
- 2022-12-22 455浏览
-
- GoLangchannel使用介绍
- 2022-12-22 440浏览
-
- Go并发控制Channel使用场景分析
- 2022-12-23 459浏览
-
- Golang的select多路复用及channel使用操作
- 2022-12-24 260浏览
-
- Go语言的Channel遍历方法详解
- 2022-12-26 290浏览
