go chan底层分析
go chan底层分析
- 底层源码
- hchan
- makechan 方法
- 环形队列
- 阻塞机制
- 向管道写数据
- 流程图
- 源码
- 从管道读数据
- 流程图
- 源码
- 关闭通道
底层源码
hchan
type hchan struct {qcount uint // 当前队列中剩余元素个数dataqsiz uint // 环形队列长度,即可以存放的元素个数,也就是通道的缓冲区大小buf unsafe.Pointer // 是一个指向环形队列的指针elemsize uint16 // 存储通道中每个元素的大小,单位是字节。closed uint32 // 标识关闭状态elemtype *_type // 元素类型sendx uint // 队列下标,指示元素写入时存放到队列中的位置recvx uint // 队列下标,指示元素从队列的该位置读出recvq waitq // 等待读消息的goroutine队列sendq waitq // 等待写消息的goroutine队列lock mutex // 互斥锁,chan不允许并发读写
}
读消息协程队列(recvq) 和 写消息协程队列(sendq) 分别是接收(<- channel))和 发送(channel <- xxx)的 协程 抽象出来的结构体(sudog)的队列,是个双向链表。
type waitq struct {first *sudoglast *sudog
}
makechan 方法
makechan 是一个内部方法,用于创建通道。它位于 src/runtime 目录下,负责通道内存的分配、初始化通道结构体等操作。makechan 方法是由 Go 运行时调用的,它不会直接出现在普通用户代码中,而是与 Go 的低级运行时管理密切相关。
创建一个管道会在 heap 中实例化一个 hchan 对象,并返回这个对象的指针。
func makechan(t *chantype, size int) *hchan {// t 是由 Go 编译器在编译时生成的。// elem 是通道元素的类型描述符(*chantype),它包含了关于通道元素类型的各种信息。// elem.Size_ 是 elem 结构体中的字段,表示通道元素类型的大小(以字节为单位)。// 例如:如果通道的元素类型是 int,那么 elem.Size_ 就是 int 类型的大小(通常是 4 字节或 8 字节,具体取决于平台)。大小是编译时确定的,并通过 elem.Size_ 字段存储在 chantype 中。elem := t.Elem// 1.元素大小检查:查通道中元素的大小是否超过了 64KB(1 << 16)。通道中每个元素的大小不能超过 64KB,超出此限制会导致不合法的元素类型。if elem.Size_ >= 1<<16 {throw("makechan: invalid channel element type")}// 2.对齐条件检查:检查 hchan 结构体的大小和元素的对齐要求是否符合系统的对齐规则。如果不符合,将抛出异常。if hchanSize%maxAlign != 0 || elem.Align_ > maxAlign {throw("makechan: bad alignment")}// 3.计算所需内存:计算通道缓冲区所需的内存大小。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"))}// 4.内存分配:(mallocgc 函数,它会在 Go 的垃圾回收器中分配内存。mallocgc 会根据需要将内存注册到垃圾回收系统,并处理内存的初始化)var c *hchanswitch {// mem == 0:如果元素大小为 0(即元素为零字节),则只分配 hchan 结构体所需的内存。case mem == 0:c = (*hchan)(mallocgc(hchanSize, nil, true))c.buf = c.raceaddr()// elem.PtrBytes == 0:如果元素类型不包含指针(即元素是简单数据类型),则通道和缓冲区内存会一次性分配。case elem.PtrBytes == 0:c = (*hchan)(mallocgc(hchanSize+mem, nil, true))c.buf = add(unsafe.Pointer(c), hchanSize)// 其他情况:如果元素类型包含指针,则首先为 hchan 分配内存,然后单独为元素数据(缓冲区)分配内存。default:c = new(hchan)c.buf = mallocgc(mem, elem, true)}// 5.初始化通道信息c.elemsize = uint16(elem.Size_) // 存储单个元素的大小c.elemtype = elem // 存储元素类型的描述信息c.dataqsiz = uint(size) // 存储缓冲区的大小(即通道中可以存储的元素数量)lockInit(&c.lock, lockRankHchan) // 初始化 hchan 结构体中的锁,用于保证并发操作时的同步// 6.调试输出:如果启用了调试模式,Go 运行时会打印通道创建的信息,用于调试。if debugChan {print("makechan: chan=", c, "; elemsize=", elem.Size_, "; dataqsiz=", size, "\n")}return c
}
环形队列
chan内部实现了一个环形队列作为其缓冲区,队列的长度是创建chan时指定的。
下图展示了一个可缓存6个元素的channel示意图:

- dataqsiz 表示了队列长度为6,即可缓存6个元素;
- buf 指向队列的内存;
- qcount 表示队列中还有两个元素;
- sendx 表示后续写入的数据存储的位置,取值[0, 6);
- recvx 表示从该位置读取数据, 取值[0, 6);

阻塞机制
-
一个协程向一个 管道读数据,如果管道缓冲区为空或者没有缓冲区,当前的协程会被加入到
读消息协程队列(recvq)中,并且被挂起来,直到对应的条件满足时(例如缓冲区有数据),它会被唤醒并继续执行; -
一个协程向一个管道写数据,如果管道缓冲区已经满了或者没有缓冲区,当前的协程会被加入到
写消息协程队列(sendq)中,并且被挂起来,直到对应的条件满足时(例如缓冲区有空间),它会被唤醒并继续执行。

注意:处于等待队列中的协程会在其他协程操作管道时被唤醒,具体如下,
- 因读阻塞的协程会被向管道写人数据的协程唤醒。
- 因写阻塞的协程会被从管道读数据的协程唤醒。
注意:一般不会出现
读消息协程队列(recvq)和写消息协程队列(sendq)中同时有协程排队的情况,只有一个例外,那就是同一个协程使用 select 语句向管道一边写数据、一边读数据,此时协程会分别位于两个等待队列中。
向管道写数据
向一个管道中写数据的过程如下:
- 如果缓冲区中有空余位置,则将数据写人缓冲区,结束写消息过程。
- 如果缓冲区中没有空余位置,则将
写消息协程加人写消息协程队列(sendq),进入睡眠并等待被读协程唤醒。 - 特殊情况:直接将准备写的数据传递给
读消息协程队列(recvq)。具体如下,当读消息协程队列(recvq)中有协程等待时,会将准备写的数据直接传递给读消息协程队列(recvq)中的第一个读消息协程,而不需要通过缓冲区。这是一个优化手段,避免了无谓的缓冲区操作。
流程图

注意:写消息的时候,如果是无缓冲管道,直接写消息,而且
读消息协程队列中没有协程,这个时候就会直接阻塞报错,要确保在写消息前读消息协程队列不为空。
源码
特殊情况:
直接将数据传递给读消息协程队列(recvq)。如果读消息协程队列(recvq)中有读消息协程等待接收数据,那么直接将发送的数据传递给读消息协程,跳过缓冲区。
阻塞操作:
如果管道缓冲区已满,并且没有读消息协程队列(recvq)中的读消息协程在等待,则发送操作会被阻塞,直到有空间可以写入数据或者接收协程完成了数据接收。
协程阻塞和唤醒机制:
在写消息协程被阻塞时(即没有缓冲区了,而且读消息协程队列(recvq)中为空),程序会将其添加到写消息协程队列(sendq)中,并通过 gopark 将其置于等待状态。
从管道读数据
从一个管道读数据的简单过程如下:
- 如果缓冲区中有数据,则从缓冲区取出数据,结束读消息过程。
- 如果缓冲区中没有数据,则将
读消息协程加入读消息协程队列(recvq),进入睡眠并等待被写消息协程唤醒。
同样,在实现时有个小技巧:如果 写消息协程队列(sendq) 不为空,且没有缓冲区,那么此时将直接从 写消息协程队列(sendq) 的第一个写消息协程 中获取数据。
流程图

源码
通道已关闭且没有数据:如果通道已关闭,且缓冲区没有数据(
qcount == 0),接收者会清理数据(如果存在的话),释放锁,然后返回。通道已关闭但缓冲区有数据:如果通道已关闭,但缓冲区中有数据,接收者可以正常接收数据。
通道未关闭且有等待发送的数据:如果通道未关闭,并且
写消息协程队列(sendq)中有等待写的消息,接收者将直接从写消息协程队列(sendq)中获取数据。
略…

关闭通道
关闭管道时会把 读消息协程队列(recvq) 中的 读消息协程 全部唤醒,这些协程获取的数据都为对应类型的零值。同时还会把 写消息协程队列(sendq) 中的 写消息协程 全部唤醒,但这些协程会触发 panic。
除此之外,其他会触发 panic 的操作还有:
- 关闭值为nil 的管道。
- 关闭已经被关闭的管道。
- 向已经关闭的管道写入数据。
参考:
go专家编程
图解Go的channel底层实现
【幼麟实验室】Golang合辑
相关文章:
go chan底层分析
go chan底层分析 底层源码hchanmakechan 方法 环形队列阻塞机制向管道写数据流程图源码 从管道读数据流程图源码 关闭通道 底层源码 hchan type hchan struct {qcount uint // 当前队列中剩余元素个数dataqsiz uint // 环形队列长度,即可以…...
idea上git log面板的使用
文章目录 各种颜色含义具体的文件的颜色标签颜色🏷️ 节点和路线 各种颜色含义 具体的文件的颜色 红色:表示还没有 git add 提交到暂存区绿色:表示已经 git add 过,但是从来没有 commit 过蓝色:表示文件有过改动 标…...
WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现)
WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现) 目录 WOA-Transformer鲸鱼算法优化编码器时间序列预测(Matlab实现)预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现WOA-Transformer鲸鱼算法优化编…...
dock 制作 python环境
报错 :Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 解决方法 配置加速地址 vim /etc/docker/daemon.json 添加以下内容 { "registry-mirror…...
2025第3周 | json-server的基本使用
目录 1. json-server是什么?2. json-server怎么用?2.1 安装2.2 创建db.json2.3 启动服务2.4 查看效果 3. 前端进行模拟交互3.1 创建demo.html3.2 创建demo.js 2025,做想做的事,读想读的书,持续学习,自律生活…...
Autodl转发端口,在本地机器上运行Autodl服务器中的ipynb文件
通过 SSH 隧道将远程端口转发到本地机器 输入服务器示例的SSH指令和密码,将远程的6006端口代理到本地 在服务器终端,激活conda虚拟环境 conda activate posecnnexport PYOPENGL_PLATFORMegljupyter notebook --no-browser --port6006 --allow-root从…...
flutter Get GetMiddleware 中间件不起作用问题
当使用 get: ^5.0.0-release-candidate-9.2.1最新版本时,中间件GetMiddleware各种教程都是让我们在redirect中实现,比如: overrideRouteSettings? redirect(String? route) {return RouteSettings(name: "/companyAuthIndexPage"…...
RabbitMQ(三)
RabbitMQ中的各模式及其用法 工作队列模式一、生产者代码1、封装工具类2、编写代码3、发送消息效果 二、消费者代码1、编写代码2、运行效果 发布订阅模式一、生产者代码二、消费者代码1、消费者1号2、消费者2号 三、运行效果四、小结 路由模式一、生产者代码二、消费者代码1、消…...
【Python】Python之locust压测教程+从0到1demo:基础轻量级压测实战(1)
文章目录 一、什么是Locust二、Locust 架构组成三、实战 Demo准备一个可调用的接口编写一个接口测试用例编写一个性能测试用例执行性能测试用例代码1、通过 Web UI 执行(GUI模式)2、通过命令行执行(非GUI模式) 小知识:…...
【JavaScript】基础内容,HTML如何引用JavaScript, JS 常用的数据类型
HTML 嵌入 Javascript 的方式 引入外部 js 文件 <head> <script Language "javaScript" src"index.js"/> </head>内部声明 <head> <script language"javascript">function hello(){alert("hello word&qu…...
vue使用自动化导入api插件unplugin-auto-import,避免频繁手动导入
unplugin-auto-import是一个现代的自动导入插件,旨在简化前端开发中的导入过程,减少手动导入的繁琐工作,提升开发效率。它支持多种构建工具,包括Vite、Webpack、Rollup和esbuild,并且可以与TypeScript配合使用&…...
在 C# 中的Lambda 表达式
在 C# 中,Lambda 表达式是用来定义匿名函数的一种简洁方式,通常用于简化代码,尤其是在 LINQ 查询、事件处理或方法作为参数的场景中。Lambda 表达式的语法如下: 基本语法 (parameters) > expression_or_statement_blockparam…...
奉加微PHY6230兼容性:部分手机不兼容
从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…...
32单片机综合应用案例——基于GPS的车辆追踪器(三)(内附详细代码讲解!!!)
困难不会永远存在,只要你勇于面对,坚持努力,就一定能够战胜一切困难。每一次挑战都是一次成长的机会,不要害怕失败,失败是成功之母。只有经历过失败,你才能更加明白自己的不足,并不断改进自己&a…...
45_Lua模块与包
Lua中的模块系统是该语言的一个重要特性,它允许开发者将代码分割成更小、更易于管理的部分。通过使用模块,你可以创建可重用的代码片段,并且可以降低代码间的耦合度。下面我将详细介绍Lua模块的基本概念、语法以及一些实际案例。 1.Lua模块 1.1 模块的基本概念 从Lua 5.1…...
深度学习电影推荐-CNN算法
文章目录 前言视频演示效果1.数据集环境配置安装教程与资源说明1.1 ML-1M 数据集概述1.1.1数据集内容1.1.2. 数据集规模1.1.3. 数据特点1.1.4. 文件格式1.1.5. 应用场景 2.模型架构3.推荐实现3.1 用户数据3.2 电影数据3.3 评分数据3.4 数据预处理3.5实现数据预处理3.6 加载数据…...
【Git 】探索 Git 的魔法——git am 与补丁文件的故事
在日常的开发协作中,你可能会遇到这样的场景:某位热心的小伙伴发来一份 .patch 文件,让你把某个问题修复合并到项目中。如果你不知道如何优雅地接收并应用这份补丁,那么这篇文章就是为你准备的!让我们一起揭开 Git 的“…...
G1原理—5.G1垃圾回收过程之Mixed GC
大纲 1.Mixed GC混合回收是什么 2.YGC可作为Mixed GC的初始标记阶段 3.Mixed GC并发标记算法详解(一) 4.Mixed GC并发标记算法详解(二) 5.Mixed GC并发标记算法详解(三) 6.并发标记的三色标记法 7.三色标记法如何解决错标漏标问题 8.SATB如何解决错标漏标问题 9.重新梳…...
机器人传动力系统介绍
电驱动系统 无框力矩电机减速器:优点是功率密度高,可在有限空间产生大扭矩,使机器人关节运动有力灵活,如人形机器人四肢运动。缺点是系统复杂,成本高,减速器会降低传动效率.空心杯电机行星滚柱丝杆&#x…...
1161 Merging Linked Lists (25)
Given two singly linked lists L1a1→a2→⋯→an−1→an and L2b1→b2→⋯→bm−1→bm. If n≥2m, you are supposed to reverse and merge the shorter one into the longer one to obtain a list like a1→a2→bm→a3→a4→bm−1⋯. For ex…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...




