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…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...

Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...