Golang的channel
目录
基本使用
channel 数据结构
阻塞的协程队列
协程节点
构建 channel
写流程
读流程
非阻塞与阻塞
closechan(关闭)
基本使用
创建无缓存 channel
c := make(chan int) //创建无缓冲的通道 cc := make(chan int,0) //创建无缓冲的通道 c
创建有缓存 channel
c := make(chan int, 3) //创建无缓冲的通道 c

例子:
package mainimport ("fmt""time"
)func main() {c := make(chan int, 3) //创建有缓冲的通道 c//内置函数 len 返回未被读取的缓冲元素数量,cap 返回缓冲区大小fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))go func() {defer fmt.Println("子go程结束")for i := 0; i < 3; i++ {c <- ifmt.Printf("子go程正在运行[%d]: len(c)=%d, cap(c)=%d\n", i, len(c), cap(c))}}()time.Sleep(2 * time.Second) //延时2sfor i := 0; i < 3; i++ {num := <-c //从c中接收数据,并赋值给numfmt.Println("num = ", num)}fmt.Println("main进程结束")
}
channel 数据结构
type hchan struct {qcount uint // total data in the queuedataqsiz uint // size of the circular queuebuf unsafe.Pointer // points to an array of dataqsiz elementselemsize uint16closed uint32elemtype *_type // element typesendx uint // send indexrecvx uint // receive indexrecvq waitq // list of recv waiterssendq waitq // list of send waiterslock mutex
}

hchan:channel 数据结构
• qcount:当前 channel 中存在多少个元素;
• dataqsize: 当前 channel 能存放的元素容量;
• buf:channel 中用于存放元素的环形缓冲区;
• elemsize:channel 元素类型的大小;
• closed:标识 channel 是否关闭;
• elemtype:channel 元素类型;
• sendx:发送元素进入环形缓冲区的 index;
• recvx:接收元素所处的环形缓冲区的 index;
• recvq:因接收而陷入阻塞的协程队列;
• sendq:因发送而陷入阻塞的协程队列;
lock mutex 锁
阻塞的协程队列
type waitq struct {first *sudoglast *sudog
}
waitq:阻塞的协程队列
• first:队列头部
• last:队列尾部
协程节点
sudog:用于包装协程的节点
type sudog struct {g *gnext *sudogprev *sudogelem unsafe.Pointer // data element (may point to stack)isSelect boolc *hchan
}

• g:goroutine,协程;
• next:队列中的下一个节点;
• prev:队列中的前一个节点;
• elem: 读取/写入 channel 的数据的容器;
• isSelect:标识当前协程是否处在 select 多路复用的流程中;
• c:标识与当前 sudog 交互的 chan.
构建 channel
func makechan(t *chantype, size int) *hchan {elem := t.elem// ...mem, overflow := math.MulUintptr(elem.size, uintptr(size))if overflow || mem > maxAlloc-hchanSize || size < 0 {panic(plainError("makechan: size out of range"))}var c *hchanswitch {case mem == 0:// Queue or element size is zero.c = (*hchan)(mallocgc(hchanSize, nil, true))// Race detector uses this location for synchronization.c.buf = c.raceaddr()case elem.ptrdata == 0:// Elements do not contain pointers.// Allocate hchan and buf in one call.c = (*hchan)(mallocgc(hchanSize+mem, nil, true))c.buf = add(unsafe.Pointer(c), hchanSize)default:// Elements contain pointers.c = new(hchan)c.buf = mallocgc(mem, elem, true)}c.elemsize = uint16(elem.size)c.elemtype = elemc.dataqsiz = uint(size)lockInit(&c.lock, lockRankHchan)return
}

写流程
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {// ...//加锁lock(&c.lock)// ...//写时存在阻塞读协程if sg := c.recvq.dequeue(); sg != nil {// Found a waiting receiver. We pass the value we want to send// directly to the receiver, bypassing the channel buffer (if any).send(c, sg, ep, func() { unlock(&c.lock) }, 3)return true}//写时不存在阻塞读协程,且缓冲区不满仍有空间if c.qcount < c.dataqsiz {// Space is available in the channel buffer. Enqueue the element to send.qp := chanbuf(c, c.sendx)typedmemmove(c.elemtype, qp, ep)c.sendx++if c.sendx == c.dataqsiz {c.sendx = 0}c.qcount++unlock(&c.lock)return true}//写时不存在阻塞读协程,且缓冲区满了没有空间// ...gp := getg()mysg := acquireSudog()mysg.elem = epmysg.g = gpmysg.c = cgp.waiting = mysgc.sendq.enqueue(mysg)atomic.Store8(&gp.parkingOnChan, 1)gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)gp.waiting = nilclosed := !mysg.successgp.param = nilmysg.c = nilreleaseSudog(mysg)return true
}

总结:
1.当写时存在阻塞读协程,我们直接用
2.当写时不存在阻塞读协程,且缓冲区不满仍有空间时,我们直接加入环形缓冲区中
3.当写时不存在阻塞读协程,且缓冲区满了没用空间时,加入阻塞写协程队列中
注意:
1.有阻塞读协程和缓冲区满之间只有一个条件符合
2.对于未初始化的 chan,写入操作会引发死锁
3.对于已关闭的 chan,写入操作会引发 panic.
读流程
读流程与写流程差不多,不同点:
1.加入的是阻塞读队列
2.当环形缓冲区有和无数据时会有不同的操作
注意:
1.读空channel, park挂起,引起死锁
2.channel 已关闭且内部无元素,直接解锁返回
非阻塞与阻塞
区别:
非阻塞模式下,读/写 channel 方法通过一个 bool 型的响应参数,用以标识是否读取/写入成功.
• 所有需要使得当前 goroutine 被挂起的操作,在非阻塞模式下都会返回 false;
• 所有是的当前 goroutine 会进入死锁的操作,在非阻塞模式下都会返回 false;
• 所有能立即完成读取/写入操作的条件下,非阻塞模式下会返回 true.
何时进入非阻塞
默认情况下,读/写 channel 都是阻塞模式,只有在 select 语句组成的多路复用分支中,
与 channel 的交互会变成非阻塞模式:
在sudog:用于包装协程的节点
• isSelect:标识当前协程是否处在 select 多路复用的流程中;
closechan(关闭)
func closechan(c *hchan) {if c == nil {panic(plainError("close of nil channel"))}lock(&c.lock)if c.closed != 0 {unlock(&c.lock)panic(plainError("close of closed channel"))}c.closed = 1var glist gList// release all readersfor {sg := c.recvq.dequeue()if sg == nil {break}if sg.elem != nil {typedmemclr(c.elemtype, sg.elem)sg.elem = nil}gp := sg.ggp.param = unsafe.Pointer(sg)sg.success = falseglist.push(gp)}// release all writers (they will panic)for {sg := c.sendq.dequeue()if sg == nil {break}sg.elem = nilgp := sg.ggp.param = unsafe.Pointer(sg)sg.success = falseglist.push(gp)}unlock(&c.lock)// Ready all Gs now that we've dropped the channel lock.for !glist.empty() {gp := glist.pop()gp.schedlink = 0goready(gp, 3)
关闭未初始化过的 channel 会 panic;
• 加锁;
• 重复关闭 channel 会 panic;
• 将阻塞读协程队列中的协程节点统一添加到 glist;
• 将阻塞写协程队列中的协程节点统一添加到 glist;
• 唤醒 glist 当中的所有协程.
防止还有协程挂起,没有被唤醒的资源浪费
参考:小徐先生1212 -- Golang Channel实现原理
相关文章:
Golang的channel
目录 基本使用 channel 数据结构 阻塞的协程队列 协程节点 构建 channel 写流程 读流程 非阻塞与阻塞 closechan(关闭) 基本使用 创建无缓存 channel c : make(chan int) //创建无缓冲的通道 cc : make(chan int,0) //创建无缓冲的通道 c 创建有缓存 channel c : m…...
DIYGW可视化开发工具:微信小程序与多端应用开发的利器
一、引言 随着移动互联网的飞速发展,微信小程序以其轻便、易用和跨平台的特点受到了广泛关注。然而,微信小程序的开发相较于传统的H5网页开发,在UI搭建和交互设计上存在一定的挑战。为了应对这些挑战,开发者们一直在寻找更加高效…...
docker——基础知识
简介 一、什么是虚拟化和容器化 实体计算机叫做物理机,有时也称为寄主机; 虚拟化:将一台计算机虚拟化为多台逻辑计算机; 容器化:一种虚拟化技术,操作系统的虚拟化;将用户空间软件实…...
SAP MMRV/MMPV 物料账期月结月底月初开关
公告:周一至周五每日一更,周六日存稿,请您点“关注”和“在看”,后续推送的时候不至于看不到每日更新内容,感谢。 这是一条刮刮乐,按住全部选中:点关注的人最帅最美,欢迎࿱…...
五分钟看懂如何解决FP独立站的广告投放问题
在数字化时代的浪潮中,跨境电商的独立站成为了商家们的新宠。与传统的电商平台相比,独立站在品牌建设、市场定位以及客户体验上提供了更多的自由度和创新空间。然而,这些独立站尤其是销售FP产品的站点,在广告投放上遇到了重重障碍…...
学习分享-FutureTask
前言 今天再改简历的时候回顾了之前实习用到的FutureTask,借此来回顾一下相关知识。 FutureTask 介绍 FutureTask 是 Java 并发包(java.util.concurrent)中的一个类,用于封装异步任务。它实现了 RunnableFuture 接口࿰…...
Javaweb02-XML概述
第一章 XML概述 1.XML基本概念 什么是xml? **a.**引入的原因:为了解决不同不同语言之间的数据传输的格式不同 **b.**概念:XML是一种可扩展标记语言,适用于不同数据之间的数据交换 **c.**XML文档:通过元素的嵌套&a…...
Linux shell编程基础
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问 Linux 内核的服务。 Shell 脚本&#x…...
2024.6.12 作业 xyt
今日课堂练习:vector构造函数 #include <iostream> #include <vector> using namespace std;void printVector(vector<int> &v) {vector<int>::iterator iter;for(iterv.begin(); iter ! v.end(); iter){cout << *iter <<…...
QTTabBar在重置Internet Explorer后失效
网上常见的办法是: 打开IE浏览器>>设置>>Internet选项>>高级。勾选启用第三方浏览器扩展,重启后生效。 打开IE浏览器-设置–管理加载项,启用QTTabBar。 实际在Win10上使用的时候会遇到点开IE自动跳转到Edge的问题。这时…...
Django之云存储(一)
一、介绍 用户上传的文件以及项目中使用的静态文件,除了保存在本地服务器,还在可以保存在云服务中,比如: 阿里云七牛云(课程选用)亚马逊云等1.1、使用方式 注册账号 七牛云开发者平台 实名认证 创建空间...
推挽与开漏输出
一般来说,微控制器的引脚都会有一个驱动电路,可以配置不同类型的数字和模拟电路接口。输出模式一般会有推挽与开漏输出。 推挽输出 推挽输出(Push-Pull Output),故名思意能输出两种电平,一种是推…...
Sora和快手可灵背后的核心技术 | 3DVAE:通过小批量特征交换实现身体和面部的三维形状变分自动编码器
【摘要】学习3D脸部和身体生成模型中一个解开的、可解释的和结构化的潜在表示仍然是一个开放的问题。当需要控制身份特征时,这个问题尤其突出。在本文中,论文提出了一种直观而有效的自监督方法来训练一个3D形状变分自动编码器(VAE),以鼓励身份特征的解开潜在表示。通过交换不同…...
ArcGIS Pro SDK (三)Addin控件 2 窗格界面类
ArcGIS Pro SDK (三)Addin控件 2 窗格界面类 目录 ArcGIS Pro SDK (三)Addin控件 2 窗格界面类15 ArcGIS Pro 后台选项卡15.1 添加控件15.2 Code15.2.1 选项卡按钮15.2.2 选项卡页 16 ArcGIS Pro 窗体16.1 添加控件16.2 Code 17 A…...
Ubuntu 20.04.6 LTS系统使用命令编辑静态IP地址【笔记】
rootubuntu-machine:/home# cat /etc/issue Ubuntu 20.04.6 LTS \n \l1、切换到root身份 sudo su2、编辑静态IP地址,示例以01-network-manager-all.yaml,个别系统可能是00-network-manager-all.yaml,以安装系统生成的文件为准。 vim /etc/n…...
Python第二语言(八、Python包)
目录 1. 什么是Python包 2. 创包步骤 2.1 new包 2.2 查看创建的包 2.3 拖动文件到包下 3. 导入包 4. 安装第三方包 4.1 什么是第三方包 4.2 安装第三方包-pip 4.3 pip网络优化 1. 什么是Python包 包下有__init__.py就是包,无__init__.py就是文件夹。于Ja…...
Pipeline流水线组件
文章目录 1、新建pipeline流水线2、定义处理器3、定义处理器上下文4、pipeline流水线实现5、处理器抽象类实现6、pipeline流水线构建者7、具体处理器实现8、流水线测试9、运行结果 1、新建pipeline流水线 package com.summer.toolkit.model.chain;import java.util.List; impo…...
闪灵CMS电子商城系统源码v5.0(自带微信小程序)
源码介绍 闪灵CMS电子商城系统源码,双语带手机版,PHPMYSQL进行开发,网站安装简单、快捷。 闪灵CMS系统更新日志 1.修复:修复了开启强制https后,说明文档重定向过多的问题 2.修复:修复了商品名称过长时无…...
基于SSM的旅游民宿预定系统【源码】【运行教程】
基于SSM的旅游民宿预定系统 一、项目介绍1. 游客功能2. 管理员功能3. 高级功能 二、项目技术栈三、项目运行四、项目演示总结 大家好,这里是程序猿代码之路!随着旅游业的快速发展,民宿作为一种独特的住宿方式越来越受到游客的喜爱。为了提升用…...
PgSQL技术内幕 - psql与服务端连接与交互机制
PgSQL技术内幕 - 客户端psql与服务端连接与交互机制 简单来说,PgSQL的psql客户端向服务端发起连接请求,服务端接收到请求后,fork出一个子进程,之后由该子进程和客户端进行交互,处理客户端的SQL等,并将结果返…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
Java中HashMap底层原理深度解析:从数据结构到红黑树优化
一、HashMap概述与核心特性 HashMap作为Java集合框架中最常用的数据结构之一,是基于哈希表的Map接口非同步实现。它允许使用null键和null值(但只能有一个null键),并且不保证映射顺序的恒久不变。与Hashtable相比,Hash…...
RLHF vs RLVR:对齐学习中的两种强化方式详解
在语言模型对齐(alignment)中,强化学习(RL)是一种重要的策略。而其中两种典型形式——RLHF(Reinforcement Learning with Human Feedback) 与 RLVR(Reinforcement Learning with Ver…...
window 显示驱动开发-如何查询视频处理功能(三)
D3DDDICAPS_GETPROCAMPRANGE请求类型 UMD 返回指向 DXVADDI_VALUERANGE 结构的指针,该结构包含特定视频流上特定 ProcAmp 控件属性允许的值范围。 Direct3D 运行时在D3DDDIARG_GETCAPS的 pInfo 成员指向的变量中为特定视频流的 ProcAmp 控件属性指定DXVADDI_QUER…...
Xcode 16.2 版本 pod init 报错
Xcode 版本升级到 16.2 后,项目执行 pod init 报错; ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchron…...
Python基于蒙特卡罗方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融投资中,风险管理是确保资产安全和实现稳健收益的关键环节。随着市场波动性的增加,传统…...
