Go:关于 Channel

文章目录

  • 写在前面
  • 内容
    • 模型图与代码
    • 发送流程
    • 接收流程

写在前面

本篇主要是通过 Channel 的模型图,对 Channel 的原理做一个基本的概述

内容

模型图与代码

我们先来看下 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 uint16elemtype *_type // element type// Channel 的关闭状态closed   uint32// 以下是等待队列和接收队列sendx    uint   // send indexrecvx    uint   // receive indexrecvq    waitq  // list of recv waiterssendq    waitq  // list of send waiters// 以下是 Channel 的锁lock mutex
}

可以看到,模型图相比于源码,主要是少了 closed 和 lock 这两个属性。
从模型图里我们可以看到,Channel 的构造主要有三部分:

  • 发送队列
  • 接收队列
  • 缓冲区

因此我们经常遇到的问题就出现在这三部分是否有无的排列组合,例如发送队列里有等待协程,接收队列里有等待协程,无缓冲区这样。

接下来我们结合模型图,并分别从发送流程和接收流程来看 Channel 的一个基本运作流程。至于模型图里没有包括的部分(锁和关闭状态)就作为补充处理。
(注:Channel 的源码位于 runtime 包下的 chan.go 文件)

发送流程

发送流程里,主要涉及到的方法是

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool

假设我们现在要发送一个数据,那么这个数据就可能有这么几种状态

  • 在发送等待队列里
  • 在缓冲区里
  • 被接收队列给拿走了

在源码里,发送数据的时候,总体流程上是

Created with Raphaël 2.3.0 检查发送等待队列 检查缓冲区 进入发送等待队列

我们可以看下:

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {...lock(&c.lock)// 向一个已经关闭的通道发送数据,会 panicif c.closed != 0 {unlock(&c.lock)panic(plainError("send on closed channel"))}// 会先检查接收队列里是否有等待接收的协程,如果有等待协程,我们就可以忽略缓冲区的两种情况:// - 没有缓冲区// - 缓冲区满// 那么意味着这里我们的数据是直接跟接收队列对接,既然接收队列里有等待协程,那就把数据给他就行了if sg := c.recvq.dequeue(); sg != nil {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)if raceenabled {racenotify(c, c.sendx, nil)}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.releasetime = 0if t0 != 0 {mysg.releasetime = -1}mysg.elem = epmysg.waitlink = nilmysg.g = gpmysg.isSelect = falsemysg.c = cgp.waiting = mysggp.param = nil// 加到发送等待队列里,然后就进入阻塞状态c.sendq.enqueue(mysg)atomic.Store8(&gp.parkingOnChan, 1)gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)KeepAlive(ep)// someone woke us up.if mysg != gp.waiting {throw("G waiting list is corrupted")}gp.waiting = nilgp.activeStackChans = falseclosed := !mysg.successgp.param = nilif mysg.releasetime > 0 {blockevent(mysg.releasetime-t0, 2)}mysg.c = nilreleaseSudog(mysg)// 再检查一下通道是否关闭了,向一个已经关闭的通道发送数据,会 panicif closed {if c.closed == 0 {throw("chansend: spurious wakeup")}panic(plainError("send on closed channel"))}return true
}

这里面关于 closed 和 lock 有一些细节:

  • 在发送开始的时候,我们需要上锁,等数据要么被拿走,要么进入缓冲区了,要么进入等待队列了,再解锁
    • 也就是操作 channel 的发送是需要加锁的
  • 如果一个 channel 被关闭了,此时还向它发送数据,会发生 panic

接收流程

接收数据里,主要涉及的方法是:

func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)

那么从接收数据的角度来看,要取一个数据,就会出现几种情况

  • 取到数据
    • 从缓冲区取
    • 从等待发送队列里取
  • 取不到数据
    • 进入等待接收队列

在源码里,它的总体流程是:

Created with Raphaël 2.3.0 检查发送等待队列 检查缓冲区 进入接收等待队列
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {... lock(&c.lock)// 如果 channel 被关闭if c.closed != 0 {// 如果缓冲区里也没有数据,就直接 return 即可if c.qcount == 0 {... unlock(&c.lock)if ep != nil {typedmemclr(c.elemtype, ep)}return true, false}} else {// 如果 channel 没关闭// 看下等待发送队列里是否有等待协程// 如果有等待发送的协程,那就再去看下缓冲区// 如果没有缓冲区,或是缓冲区没数据,就直接从发送等待队列里拿数据// 如果有缓冲区且有数据,就从缓冲区里拿数据,再把等待队列里的数据追加到缓冲队列的末尾if sg := c.sendq.dequeue(); sg != nil {recv(c, sg, ep, func() { unlock(&c.lock) }, 3)return true, true}}// 到这里就认为不去看接收队列了,直接看缓冲区// 缓冲区有数据,就直接取if c.qcount > 0 {// Receive directly from queueqp := chanbuf(c, c.recvx)if raceenabled {racenotify(c, c.recvx, nil)}if ep != nil {typedmemmove(c.elemtype, ep, qp)}typedmemclr(c.elemtype, qp)c.recvx++if c.recvx == c.dataqsiz {c.recvx = 0}c.qcount--unlock(&c.lock)return true, true}...// 想取数据却没地方可取,那就加入接收等待队列,然后就进入阻塞状态了gp := getg()mysg := acquireSudog()mysg.releasetime = 0if t0 != 0 {mysg.releasetime = -1}mysg.elem = epmysg.waitlink = nilgp.waiting = mysgmysg.g = gpmysg.isSelect = falsemysg.c = cgp.param = nilc.recvq.enqueue(mysg)atomic.Store8(&gp.parkingOnChan, 1)gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)// someone woke us upif mysg != gp.waiting {throw("G waiting list is corrupted")}gp.waiting = nilgp.activeStackChans = falseif mysg.releasetime > 0 {blockevent(mysg.releasetime-t0, 2)}success := mysg.successgp.param = nilmysg.c = nilreleaseSudog(mysg)return true, success
}

同样的,接收的过程里,也有 closed 和 lock 的一些细节:

  • 在接收过程前,需要加锁,处理完后再解锁
  • 从一个已经关闭的 channel 里取数据,不会造成 panic

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

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

相关文章

C语言编程实现只有一个未知数的两个多项式合并的程序

背景&#xff1a; 直接看题目把&#xff01;就是C语言写两个多项式多项式合并 题目要求&#xff1a; 1. 题目&#xff1a; 编程实现只有一个未知数的两个多项式合并的程序。如&#xff1a; 3x^26x7 和 5x^2-2x9合并结果为8x^24x16。 2. 设计要求 &#xff08;1&#xff09…

CSS盒子定位的扩张

定位的扩展 绝对定位&#xff08;固定定位&#xff09;会完全压住盒子 浮动元素不会压住下面标准流的文字&#xff0c;而绝对定位或固定位会压住下面标准流的所有内容 如果一个盒子既有向左又有向右&#xff0c;则执行左&#xff0c;同理执行上 显示隐藏 display: none&…

如何实现chatGPT批量问答,不用token

3分钟&#xff0c;教你做个GPT批量问答还不用token | 有源码 源码链接 解压压缩包&#xff1b;在Pycharm打开这个文件夹 执行 pip install undetected_chromedriver 和 pip install selenium 执行第1到63行代码&#xff0c;后台会自动打开浏览器&#xff0c;需要手动登录账…

Linux命令笔记

终端命令格式&#xff1a; bash command [-options] [parameter] 7个常见Linux命令&#xff1a; 01 ls | list | 查看当前文件夹下的内容 02 pwd | print work directory | 查看当前所在文件夹 03 cd [目录名] | change directory | 切换文件夹 04 touch [文件名] | touc…

android 修改输出apk的包名

一&#xff0c;打包方式使用IDE菜单选项 二、在app级别的build.gradle下配置&#xff1a; static def releaseTime() {return new Date().format("yyyyMMdd.kkmm", TimeZone.getTimeZone("GMT8")) }android.applicationVariants.all { variant ->print…

【垃圾回收概述及算法】

文章目录 1. 垃圾回收概述及算法2. 垃圾回收相关算法2.1 标记阶段&#xff1a;引用计数算法2.2 标记阶段&#xff1a;可达性分析算法2.3 对象的 finalization 机制2.3.1 一个对象是否可回收的判断 2.4 清除阶段&#xff1a;标记-清除算法2.5 清除阶段&#xff1a;复制算法2.6 清…

笔记36:CNN的多通道卷积到底是什么样的

总结&#xff1a; &#xff08;1&#xff09;输入卷积层的feature_map的通道数&#xff0c;就是该卷积层每个卷积核所含有的通道数 &#xff08;2&#xff09;输出卷积层的feature_map的通道数&#xff0c;就是该卷积层所含有的卷积核的个数 a a a a 解释&#xff1a;【…

测开 | Vue速查知识点

文章目录 Vue知识1. Vue 概述2. Vue 代码格式3. Vue 指令3.1 v-bind & v-model3.2 v-on3.3 v-if和v-show3.4 v-for 4. 生命周期 Vue知识 1. Vue 概述 简介&#xff1a; Vue.js&#xff08;读音 /vjuː/, 类似于 view&#xff09; 是一套构建用户界面的 渐进式框架。与其他…

分类预测 | MATLAB实现KOA-CNN-BiLSTM开普勒算法优化卷积双向长短期记忆神经网络数据分类预测

分类预测 | MATLAB实现KOA-CNN-BiLSTM开普勒算法优化卷积双向长短期记忆神经网络数据分类预测 目录 分类预测 | MATLAB实现KOA-CNN-BiLSTM开普勒算法优化卷积双向长短期记忆神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现KOA-CNN-BiLST…

论文解析——异构多芯粒神经网络加速器

作者 朱郭益, 马胜&#xff0c;张春元, 王波&#xff08;国防科技大学计算机学院&#xff09; 摘要 随着神经网络技术的快速发展, 出于安全性等方面考虑, 大量边缘计算设备被应用于智能计算领域。首先&#xff0c;设计了可应用于边缘计算的异构多芯粒神经网络加速器其基本结构…

Unity中Shader光强与环境色

文章目录 前言一、实现下图中的小球接受环境光照实现思路&#xff1a;1、在Pass中使用前向渲染模式2、使用系统变量 _LightColor0 获取场景中的主平行灯 二、返回环境中主环境光的rgb固定a(亮度)&#xff0c;小球亮度还随之改变的原因三、获取Unity中的环境光的颜色1、Color模式…

帮微软语音助手纠正“阿弥陀佛”“e”字错误发音的技巧

一、前言 微软AI文字转语音助手&#xff0c;现已被大家普便应用。最近在传统文化佛学名词的发音转换应用中&#xff0c;发现了一个致命的错误。那就是“阿弥陀佛”中的“阿”字的“a”发音&#xff0c;被误读为“e”。说起这个重大的错误&#xff0c;佛门大德南怀瑾老师也一再…