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);

img

阻塞机制

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

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

在这里插入图片描述

注意:处于等待队列中的协程会在其他协程操作管道时被唤醒,具体如下,

  1. 因读阻塞的协程会被向管道写人数据的协程唤醒。
  2. 因写阻塞的协程会被从管道读数据的协程唤醒。

注意:一般不会出现 读消息协程队列(recvq)写消息协程队列(sendq) 中同时有协程排队的情况,只有一个例外,那就是同一个协程使用 select 语句向管道一边写数据、一边读数据,此时协程会分别位于两个等待队列中。

向管道写数据

向一个管道中写数据的过程如下:

  1. 如果缓冲区中有空余位置,则将数据写人缓冲区,结束写消息过程。
  2. 如果缓冲区中没有空余位置,则将 写消息协程 加人 写消息协程队列(sendq),进入睡眠并等待被 读协程 唤醒。
  3. 特殊情况:直接将准备写的数据传递给 读消息协程队列(recvq) 。具体如下,当 读消息协程队列(recvq) 中有协程等待时,会将准备写的数据直接传递给 读消息协程队列(recvq) 中的第一个 读消息协程,而不需要通过缓冲区。这是一个优化手段,避免了无谓的缓冲区操作。

流程图

在这里插入图片描述

源码

特殊情况
直接将数据传递给 读消息协程队列(recvq) 。如果 读消息协程队列(recvq) 中有 读消息协程 等待接收数据,那么直接将发送的数据传递给 读消息协程 ,跳过缓冲区。
在这里插入图片描述

阻塞操作
如果管道缓冲区已满,并且没有 读消息协程队列(recvq) 中的 读消息协程 在等待,则发送操作会被阻塞,直到有空间可以写入数据或者接收协程完成了数据接收。
在这里插入图片描述

协程阻塞和唤醒机制
写消息协程 被阻塞时(即没有缓冲区了,而且 读消息协程队列(recvq) 中为空),程序会将其添加到 写消息协程队列(sendq) 中,并通过 gopark 将其置于等待状态。
在这里插入图片描述

从管道读数据

从一个管道读数据的简单过程如下:

  1. 如果缓冲区中有数据,则从缓冲区取出数据,结束读消息过程。
  2. 如果缓冲区中没有数据,则将 读消息协程 加入 读消息协程队列(recvq),进入睡眠并等待被 写消息协程 唤醒。

同样,在实现时有个小技巧:如果 写消息协程队列(sendq) 不为空,且没有缓冲区,那么此时将直接从 写消息协程队列(sendq) 的第一个写消息协程 中获取数据。

流程图

在这里插入图片描述

源码

通道已关闭且没有数据:如果通道已关闭,且缓冲区没有数据(qcount == 0),接收者会清理数据(如果存在的话),释放锁,然后返回。

通道已关闭但缓冲区有数据:如果通道已关闭,但缓冲区中有数据,接收者可以正常接收数据。

通道未关闭且有等待发送的数据:如果通道未关闭,并且 写消息协程队列(sendq) 中有等待写的消息,接收者将直接从 写消息协程队列(sendq) 中获取数据。

在这里插入图片描述

略…

在这里插入图片描述

关闭通道

关闭管道时会把 读消息协程队列(recvq) 中的 读消息协程 全部唤醒,这些协程获取的数据都为对应类型的零值。同时还会把 写消息协程队列(sendq) 中的 写消息协程 全部唤醒,但这些协程会触发 panic。

除此之外,其他会触发 panic 的操作还有:

  1. 关闭值为nil 的管道。
  2. 关闭已经被关闭的管道。
  3. 向已经关闭的管道写入数据。

参考:
go专家编程

图解Go的channel底层实现

【幼麟实验室】Golang合辑

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/2854.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Git版本控制器--1】Git的基本操作--本地仓库

目录 初识git 本地仓库 认识工作区、暂存区、版本库 add操作与commit操作 master文件与commit id 修改文件 版本回退 撤销修改 删除文件 初识git Git 是一个分布式版本控制系统&#xff0c;主要用于跟踪文件的更改&#xff0c;特别是在软件开发中。 为什么要版本…

【C语言】_字符串拷贝函数strcpy

目录 1. 函数声明及功能 2. 使用示例 3. 注意事项 4. 模拟实现 4.1 第一版&#xff1a;基本功能判空const修饰 4.2 第二版&#xff1a;优化对于\0的单独拷贝 4.3 第三版&#xff1a;仿strcpy的char*返回值 1. 函数声明及功能 char * strcpy ( char * destination, cons…

AIGC时代:如何快速搞定Spring Boot+Vue全栈开发

文章目录 一、Spring Boot基础二、Vue.js基础三、Spring Boot与Vue.js集成四、性能优化与最佳实践《快速搞定Spring BootVue全栈开发》 内容简介作者简介目录前言/序言本书内容本书特点读者对象 随着人工智能生成内容&#xff08;AIGC&#xff09;技术的迅速发展&#xff0c;…

C语言结构体漫谈:从平凡中见不平凡

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文《1》 结构体的两种声明一、结构…

lwip单网卡多ip的实现

1、今天要实现lwip的多个ip配置&#xff0c;本来以为需要自己修改很多核心代码 2、查阅资料才发现&#xff0c;lwip已经把接口留出来了 /** Define this to 1 and define LWIP_ARP_FILTER_NETIF_FN(pbuf, netif, type) * to a filter function that returns the correct neti…

MySQL NaviCat 安装及配置教程(Windows)【安装】

文章目录 一、 MySQL 下载 1. 官网下载2. 其它渠道 二、 MySQL 安装三、 MySQL 验证及配置四、 NaviCat 下载 1. 官网下载2. 其它渠道 五、 NaviCat 安装六、 NaviCat 逆向工程 软件 / 环境安装及配置目录 一、 MySQL 下载 1. 官网下载 安装地址&#xff1a;https://www.m…

HarmonyOS Next 实现登录注册页面(ARKTS) 并使用Springboot作为后端提供接口

1. HarmonyOS next ArkTS ArkTS围绕应用开发在 TypeScript &#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集 ArkTS在TS的基础上扩展了struct和很多的装饰器以达到描述UI和状态管理的目的 以下代码是一个基于…

Web第一次作业

主页: <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title>主页</title> </head> <body> <h1>你好&#xff01; 来到我的网站</h1> <p><a href"login…

大疆最新款无人机发布,可照亮百米之外目标

近日&#xff0c;DJI 大疆发布全新小型智能多光旗舰 DJI Matrice 4 系列&#xff0c;包含 Matrice 4T 和 Matrice 4E 两款机型。DJI Matrice 4E 价格为27888 元起&#xff0c;DJI Matrice 4T价格为38888元起。 图片来源&#xff1a;大疆官网 DJI Matrice 4E DJI Matrice 4T D…

掌握C语言内存布局:数据存储的智慧之旅

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 目录 引言正文一、数据类型介绍1.内置类型2.自定义…

图论的起点——七桥问题

普瑞格尔河从古堡哥尼斯堡市中心流过&#xff0c;河中有小岛两座&#xff0c;筑有7座古桥&#xff0c;哥尼斯堡人杰地灵&#xff0c;市民普遍爱好数学。1736年&#xff0c;该市一名市民向大数学家Euler提出如下的所谓“七桥问题”&#xff1a; 从家里出发&#xff0c;7座桥每桥…

ubuntu20.04安装MySQL5.7

deb安装 下载deb文件并配置 wget https://repo.mysql.com//mysql-apt-config_0.8.12-1_all.deb sudo dpkg -i mysql-apt-config_0.8.12-1_all.deb我使用xshell可以正常。 这个弹出框里&#xff0c;选择的是“ubuntu bionic”。(在终端工具上&#xff0c;有可能显示不了选项)【…

openharmony标准系统方案之瑞芯微RK3568移植案例

标准系统方案之瑞芯微RK3568移植案例 ​本文章是基于瑞芯微RK3568芯片的DAYU200开发板&#xff0c;进行标准系统相关功能的移植&#xff0c;主要包括产品配置添加&#xff0c;内核启动、升级&#xff0c;音频ADM化&#xff0c;Camera&#xff0c;TP&#xff0c;LCD&#xff0c…

【C语言】_求字符串长度函数strlen

目录 1. 函数声明及功能 2. 注意事项 3. 模拟实现 3.1 方式1&#xff1a;计数器方式 3.2 方式2&#xff1a;指针-指针方式 3.3 方式3&#xff1a;递归方式&#xff08;不创建临时变量计数器方式&#xff09; 4. strlen相关例题 1. 函数声明及功能 size_t strlen ( cons…

【大前端】Vue3 工程化项目使用详解

目录 一、前言 二、前置准备 2.1 环境准备 2.1.1 create-vue功能 2.1.2 nodejs环境 2.1.3 配置nodejs的环境变量 2.1.4 更换安装包的源 三、工程化项目创建与启动过程 3.1 创建工程化项目 3.2 项目初始化 3.3 项目启动 3.4 核心文件说明 四、VUE两种不同的API风格 …

微软开源AI Agent AutoGen 详解

AutoGen是微软发布的一个用于构建AI Agent系统的开源框架,旨在简化事件驱动、分布式、可扩展和弹性Agent应用程序的创建过程。 开源地址: GitHub - microsoft/autogen: A programming framework for agentic AI 🤖 PyPi: autogen-agentchat Discord: https://aka.ms/auto…

cursor重构谷粒商城02——30分钟构建图书管理系统【cursor使用教程番外篇】

前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶到中高阶程序员。 本项目将基于谷粒商城…

[Qualcomm]Qualcomm MDM9607 SDK代码下载操作说明

登录Qualcomm CreatePoing Qualcomm CreatePointhttps://createpoint.qti.qua

【15】Word:互联网发展状况❗

目录 题目​ NO2 NO3 NO4 NO5 NO6 NO7.8.9 NO7 NO8 NO9 NO10 题目 NO2 布局→页面设置→纸张&#xff1a;A4→页边距&#xff1a;上下左右→版式&#xff1a;页眉/页脚页码范围&#xff1a;多页&#xff1a;对称页边距→内侧/外侧→装订线 NO3 首先为文档应用内置…

ROS1学习记录

我使用的是ubuntu20.04下的ROS Noetic版本&#xff0c;是ROS 1 的最后一个长期支持&#xff08;LTS&#xff09;版本&#xff0c;将于2025年5月停止维护 一&#xff0c;Linux系统基本操作 ctrlaltt快速打开终端 1&#xff0c;pwd命令 查看当前终端所在路径 使用方式&#…