go Channel原理 (二)

Channel

设计原理

不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存。

在主流编程语言中,多个线程传递数据的方式一般都是共享内存。
在这里插入图片描述
Go 可以使用共享内存加互斥锁进行通信,同时也提供了一种不同的并发模型,即通信顺序进程(Communicating sequential processes,CSP)。Goroutine 和 Channel 分别对应 CSP 中的实体和传递信息的媒介,Goroutine 之间会通过 Channel 传递数据。
在这里插入图片描述
上图中的两个 Goroutine,一个会向 Channel 中发送数据,另一个会从 Channel 中接收数据,它们两者能够独立运行并不存在直接关联,但是能通过 Channel 间接完成通信。

发送数据

两个 Goroutine,一个会向 Channel 中发送数据,另一个会从 Channel 中接收数据,它们两者能够独立运行并不存在直接关联,但是能通过 Channel 间接完成通信。这是一个 生产者 - 消费者 模型,负责传递数据的 goroutine 发送数据到 channel,channel 起到一个临界区/缓冲区的作用。

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
   // 如果 channel 是 nil
   if c == nil {
      // 不能阻塞,直接返回 false,表示未发送成功
      if !block {
         return false
      }
      // 当前 goroutine 被挂起
      gopark(nil, nil, "chan send (nil chan)", traceEvGoStop, 2)
      throw("unreachable")
   }

   // 省略 debug 相关……

   // 对于不阻塞的 send,快速检测失败场景
   //
   // 如果 channel 未关闭且 channel 没有多余的缓冲空间。这可能是:
   // 1. channel 是非缓冲型的,且等待接收队列里没有 goroutine (c.dataqsiz == 0 && c.recvq.first == nil)
   // 2. channel 是缓冲型的,但循环数组已经装满了元素 (c.dataqsiz > 0 && c.qcount == c.dataqsiz)
   
   // 这里涉及两个观测项:channel 未关闭、channel not ready for sending。
   // 这两个都会因为没加锁而出现观测前后不一致的情况。
   // 但是,因为 close channel 这个行为不能将 channel 的状态从 ready for sending 变成 not ready for sending
   // 所以当观测到 channel 的状态是 not ready for sending,channel 是不是 closed 并不重要,可以直接返回 false。
   if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
      (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
      return false
   }

   var t0 int64
   if blockprofilerate > 0 {
      t0 = cputicks()
   }

   // 锁住 channel,并发安全
   lock(&c.lock)

   // 如果 channel 关闭了
   if c.closed != 0 {
      // 解锁
      unlock(&c.lock)
      // 直接 panic
      panic(plainError("send on closed channel"))
   }

   // 如果接收队列里有 goroutine,直接将要发送的数据拷贝到接收 goroutine
   if sg := c.recvq.dequeue(); sg != nil {
      send(c, sg, ep, func() { unlock(&c.lock) }, 3)
      return true
   }

   // 对于缓冲型的 channel,如果还有缓冲空间
   if c.qcount < c.dataqsiz {
      // qp 指向 buf 的 sendx 位置
      qp := chanbuf(c, c.sendx)

      // ……

      // 将数据从 ep 处拷贝到 qp
      typedmemmove(c.elemtype, qp, ep)
      // 发送游标值加 1
      c.sendx++
      // 如果发送游标值等于容量值,游标值归 0
      if c.sendx == c.dataqsiz {
         c.sendx = 0
      }
      // 缓冲区的元素数量加一
      c.qcount++

      // 解锁
      unlock(&c.lock)
      return true
   }

   // 如果不需要阻塞,则直接返回错误
   if !block {
      unlock(&c.lock)
      return false
   }

   // channel 满了,发送方会被阻塞。接下来会构造一个 sudog

   // 获取当前 goroutine 的指针
   gp := getg()
   // 获取 sudog 并设置这一次阻塞发送的相关信息
   mysg := acquireSudog()
   mysg.releasetime = 0
   if t0 != 0 {
      mysg.releasetime = -1
   }

   mysg.elem = ep
   mysg.waitlink = nil
   mysg.g = gp
   mysg.selectdone = nil
   mysg.c = c
   gp.waiting = mysg
   gp.param = nil

   // 当前 goroutine 进入发送等待队列
   c.sendq.enqueue(mysg)

   // 当前 goroutine 被挂起
   // 这里阻塞住了
   goparkunlock(&c.lock, "chan send", traceEvGoBlockSend, 3)

   // 从这里开始被唤醒了(channel 有机会可以发送了)
   if mysg != gp.waiting {
      throw("G waiting list is corrupted")
   }
   gp.waiting = nil
   if gp.param == nil {
      if c.closed == 0 {
         throw("chansend: spurious wakeup")
      }
      // 被唤醒后,channel 关闭了。坑爹啊,panic
      panic(plainError("send on closed channel"))
   }
   gp.param = nil
   if mysg.releasetime > 0 {
      blockevent(mysg.releasetime-t0, 2)
   }
   // 释放当前 goroutine 的 sudog
   mysg.c = nil
   releaseSudog(mysg)
   return true
}

// sender -> receiver
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
    // 省略一些用不到的
    // ……
    // sg.elem 指向接收到的值存放的位置,如 val <- ch,指的就是 &val
    // ep:被发送的元素
    if sg.elem != nil {
        // 直接拷贝内存(从发送者到接收者)
        sendDirect(c.elemtype, sg, ep)
        sg.elem = nil
    }
    // sudog 上绑定的 goroutine
    gp := sg.g
    // 解锁
    unlockf()
    gp.param = unsafe.Pointer(sg)
    if sg.releasetime != 0 {
        sg.releasetime = cputicks()
    }
    // 将等待接收数据的 Goroutine 标记成可运行状态 Grunnable 
    // 并把该 Goroutine 放到发送方所在的处理器的 runnext 上等待执行
    // 该处理器在下一次调度时会立刻唤醒数据的接收方;
    goready(gp, skip+1)
}

func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
    // src 在当前 goroutine 的栈上,dst 是另一个 goroutine 的栈
    
    // 直接进行内存"搬迁"
    // 如果目标地址的栈发生了栈收缩,当我们读出了 sg.elem 后
    // 就不能修改真正的 dst 位置的值了
    // 因此需要在读和写之前加上一个屏障
    dst := sg.elem
    typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.size)
    memmove(dst, src, t.size)
}

将消息发送到 channel 的 核心函数是 chansend

  1. 当存在等待的 receiver 时,直接将数据发送给阻塞的 goroutine 并将其设置成下一个运行的 goroutine。

这里 send 的时候,涉及到一个 goroutine 直接写另一个 goroutine 栈的操作,一般而言,不同 goroutine 的栈是各自独有的。而这也违反了 GC 的一些假设。为了不出问题,写的过程中增加了写屏障,保证正确地完成写操作。这样做的好处是减少了一次内存 copy:不用先拷贝到 channel 的 buf,直接由发送者到接收者,效率得以提高。

  1. 如果 channel 存在缓冲区并且还有空闲的容量,我们会直接将数据存储到缓冲区 sendx 所在的位置上。
  2. 当不存在缓冲区或者缓冲区已满时,等待其他 goroutine 从 channel 接收数据,sender 进入等待队列并阻塞。
    发送数据的过程中包含几个会触发 goroutine 调度的时机:
  3. 发送数据时发现 channel 上存在等待接收数据的 goroutine,立刻设置处理器的 runnext 属性,但是并不会立刻触发调度。
  4. 发送数据时并没有找到接收方并且缓冲区已经满了,这时会将自己加入 channel 的 sendq 队列并调用 runtime.goparkunlock 触发 Goroutine 的调度让出处理器的使用权。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

复兴社:凝聚多方力量,共促乡村繁荣

复兴社自成立以来&#xff0c;始终肩负着推动全国经济发展、实现共同富裕的重任。乡村振兴作为实现这一目标的重要途径之一&#xff0c;一直是复兴社的工作重点。在李忠平会长的领导下&#xff0c;复兴社通过联合政府、企业和社会各界的资源&#xff0c;共同推进乡村振兴&#…

基于STM32的智能门锁控制系统

目录 引言环境准备智能门锁控制系统基础代码实现&#xff1a;实现智能门锁控制系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;门锁管理与优化问题解决方案与优化收尾与总结 1. 引言 智能门锁控制系统通过使用STM32嵌…

Is ChatGPT a Good Personality Recognizer? A Preliminary Study?

ChatGPT是一个很好的人格识别者吗&#xff1f;初步调研 摘要1 介绍2 背景和相关工作3 实验3.1 数据集3.2 提示策略3.3 基线3.4 评估指标3.5 实现细节3.6 Overall Performance (RQ1)3.7 ChatGPT在人格识别上的公平性 (RQ2)3.8 ChatGPT对下游任务的人格识别能力&#xff08;RQ3&a…

Java 面试指南合集

JVM 篇 线程篇 springBoot篇 SpringCloud篇 待更新 黑夜无论怎样悠长&#xff0c;白昼总会到来。 此文会一直更新哈 如果你希望成功&#xff0c;当以恒心为良友&#xff0c;以经验为参谋&#xff0c;以当心为兄弟&#xff0c;以希望为哨兵。

行业分析---造车新势力之极氪汽车

1 前言 在之前的博客中&#xff0c;笔者撰写了多篇行业类分析的文章&#xff08;科技新能源&#xff09;&#xff1a; 《行业分析---我眼中的Apple Inc.》 《行业分析---马斯克的Tesla》 《行业分析---造车新势力之蔚来汽车》 《行业分析---造车新势力之小鹏汽车》 《行业分析-…

绘图黑系配色

随便看了几篇小论文&#xff0c;里面的黑配色挺喜欢的&#xff0c;虽然平时SCI系配色用的多&#xff0c;但看到纯黑配色与黑加蓝配色&#xff0c;那就是我最心上的最优style。

【JVM】JVM 内存结构

程序计数器 Cpu 要不停的切换执行线程&#xff0c;所以在切换回同一个线程的时候要知道程序执行到哪了&#xff0c;程序计数器&#xff08;PC 计数器&#xff09;&#xff0c;用来存储指向下一条指令的地址&#xff0c;也就是将要执行的代码。 程序的分支、循环、跳转、异常处…

【论文解读】大模型的有效探索

一、简要介绍 论文提出的证据表明&#xff0c;通过有效地探索收集人类反馈以改进大型语言模型有实质性的好处。在论文的实验中&#xff0c;一个代理依次生成查询&#xff0c;同时拟合一个奖励模型的反馈收到。论文的最佳性能代理使用双汤普森抽样生成查询&#xff0c;其不确定性…

“香港世界”模型问世!颠覆传统SLAM技术,引领复杂定位新纪元!

论文标题&#xff1a; Hong Kong World: Leveraging Structural Regularity for Line-Based SLAM 论文作者&#xff1a; Haoang Li, Ji Zhao, Jean-Charles Bazin, Pyojin Kim, Kyungdon Joo, Zhenjun Zhao, Yun-Hui Liu 导读&#xff1a; 在视觉感知技术中&#xff0c;理解…

力扣 单链表元素删除解析及高频面试题

目录 删除元素的万能方法 构造虚拟头结点来应对删除链表头结点的情况 一、203.移除链表元素 题目 题解 二、19.删除链表中倒数第K个节点 题目 题解 三、 83.删除某个升序链表中的重复元素&#xff0c;使重复的元素都只出现一次 题目 题解 82.删除某个升序链表中的…

玛格家居从深交所转板北交所:营收净利润连年下滑,销售费用大增

《港湾商业观察》施子夫 近日&#xff0c;玛格家居股份有限公司&#xff08;以下简称&#xff0c;玛格家居&#xff09;发布公告&#xff0c;重庆证监局已经受理其北交所上市的备案申请&#xff0c;辅导机构为国泰君安证券。 公开信息显示&#xff0c;2022年1月&#xff0c;玛…

DreamView数据流

DreamView数据流 查看DV中界面启动dag&#xff0c;/apollo/modules/dreamview_plus/conf/hmi_modes/pnc.pb.txt可以看到点击界面的planning按钮&#xff0c;后台其实启动的是/apollo/modules/planning/planning_component/dag/planning.dag和/apollo/modules/external_command…

使用网络抓取器进行网络抓取--你需要了解的一切

什么是网页抓取&#xff1f; 网页抓取是一种计算机化过程&#xff0c;用于从网站上收集大量数据。它也常被称为网页数据提取或网页数据抓取。 网页抓取需要两个部分 - 爬虫和抓取器。 爬虫是一种AI算法&#xff0c;通过跟随互联网中的链接来搜索所需的特定数据。抓取器是一种…

Python对象不可哈希?教你几招解决!

目录 1、什么是可哈希?🚀 1.1 哈希基础理论 1.2 可哈希对象定义🔍 示例代码: 1.3 Python中哈希的作用 1.4 哈希表与性能提升📈 应用实例代码: 2、Python中的哈希特性🔑 2.1 不变性与哈希值🔄 示例代码展示: 2.2 实现细节深入探讨📚 深入代码细节:…

小区服务前台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;住户管理&#xff0c;管理员管理&#xff0c;员工管理&#xff0c;安保管理&#xff0c;安保分配管理&#xff0c;客服聊天管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;公告&#xff0c;…

【传知代码】揭秘AI如何揪出图片中的“李鬼”(论文复现)

在数字化时代&#xff0c;我们时常被各种图像信息所包围。然而&#xff0c;这些图像中有时隐藏着不为人知的秘密——被篡改的文字或图像。这些被篡改的内容可能误导我们的判断&#xff0c;甚至在某些情况下造成严重的后果。幸运的是&#xff0c;随着人工智能&#xff08;AI&…

免费开源AI生产力工具:内置专属ChatGPT、一键智能处理图片和视频(擦除水印、卡通漫画、无损放大、插值补帧、智能修复、3D转制、上色修复、合成整理)

AI 生产力工具 免费开源&#xff0c;提升用户生产力&#xff0c;保障隐私和数据安全。提供高效便捷的AI解决方案&#xff0c;包括但不限于&#xff1a;内置专属ChatGPT、一键批量智能处理图片和视频等。 主要特点 免费开源&#xff1a;免费使用&#xff0c;源代码开放&#…

使用Nginx反向代理KKFileView遇到问题

使用KKFileView 4.0 以上版本 在KKFileView官网上&#xff0c;关于使用Nginx代理&#xff0c;建议配置如下 一、修改Nacos 在Nginx的conf文件夹中修改 nginx.conf ,新加 红框内的IP地址为代理服务器地址&#xff08;即安装KKFileView的服务器地址&#xff09; 二、修改KKFil…

逻辑这回事(七)---- 器件基础

Xilinx FPGA创建了先进的硅模块(ASMBL)架构,以实现FPGA具有针对不同应用程序领域优化的各种功能组合的平台。通过这一创新,Xilinx提供了更多的设备选择,使客户能够为其特定设计选择具有正确的功能和功能组合的FPGA。ASMBL体系结构通过以下方式突破了传统的设计障碍:消除几…

一个时代的结束:Centos7将在6月30日退出历史舞台

友情提醒&#xff1a; 如果你使用的是曾经辉煌一时的CentOS Linux 7&#xff0c;一直拖延没有迁移&#xff0c;那么现在距离它正式寿终正寝还有不到一周的时间。 CentOS Linux 7 的结束日期仍定在2024年6月30日。红帽早在 2020 年就做出了有争议的举动&#xff0c;将重点转移到…