go Channel原理 (二)

news2024/11/19 3:30:38

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.coloradmin.cn/o/1882260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OSRAM欧司朗XBO短弧氙灯160WHSXLOFR短弧氙灯450W

OSRAM欧司朗XBO短弧氙灯160WHSXLOFR短弧氙灯450W

Pycharm中安装Pytorch的库

step1&#xff1a; step2&#xff1a; step3&#xff1a; pip install torch torchvisionstep4&#xff1a; pip install numpy<2 # 版本低点&#xff0c;和pytorch版本不兼容&#xff0c;我当时是用的2.0的step5&#xff1a; pip install pandasstep6&#xff1a; …

2.00004 优化器执行计划生成的流程是怎么样的?

文章目录 整体架构关键结构体PlannerInfo (pathnodes.h:195)PlannerGlobal (pathnodes.h:95)函数栈关键函数pg_plan_query (postgres.c:885)planner (planner.c:274)standard_planner (planner.c:287)subquery_planner (planner.c:628)整体架构 关键结构体 PlannerInfo (pathn…

【电源拓扑】PFC

为什么开关电源中都有PFC电路 PFC电路就是功率矫正电路&#xff0c;目的是为了防止杂波对电网产生冲击 AC220V通过整流桥之后电压和电流的波形分析 PFC电路为什么选择是Boost升压电路 PFC电路为什么要把电压升高到400V 为了解决输入电压低于滤波电容电压这个矛盾&#xff0…

2024攻防演练:亚信安全推出MSS/SaaS短期定制服务

随着2024年攻防演练周期延长的消息不断传出&#xff0c;各参与方将面临前所未有的挑战。面对强大的攻击队伍和日益严格的监管压力&#xff0c;防守单位必须提前进行全面而周密的准备和部署。为应对这一形势&#xff0c;亚信安全特别推出了为期三个月的MSS/SaaS短期订阅方案。该…

《昇思25天学习打卡营第8天|CarpeDiem》

《昇思25天学习打卡营第8天|CarpeDiem》 模型训练构建数据集定义神经网络模型定义超参、损失函数和优化器超参损失函数优化器 训练与评估 打卡 今天是昇思25天学习打卡营的第8天&#xff0c;终于迎来 模型训练 的部分了&#xff01;&#xff01;&#xff01; 兴奋 发癫 模型训…

2065. 最大化一张图中的路径价值 Hard

给你一张 无向 图&#xff0c;图中有 n 个节点&#xff0c;节点编号从 0 到 n - 1 &#xff08;都包括&#xff09;。同时给你一个下标从 0 开始的整数数组 values &#xff0c;其中 values[i] 是第 i 个节点的 价值 。同时给你一个下标从 0 开始的二维整数数组 edges &#xf…

SSL证书的重要作用和申请方法

SSL&#xff08;Secure Sockets Layer&#xff09;证书作为一种基础而强大的网络安全工具&#xff0c;扮演着保护数据传输安全、建立用户信任的桥梁角色。本文将深入探讨SSL证书的主要作用&#xff0c;并详细介绍其申请方法&#xff0c;以帮助网站所有者为用户提供一个更加安全…

Linux下编程之内存检查

前言 我们在进行编程时&#xff0c;有时不免会无意中写出一些容易导致内存问题&#xff08;可能一时表象上正常&#xff09;的代码&#xff0c;导致的后果肯定是不好的&#xff0c;就像一颗颗“哑弹”&#xff0c;令人心慌。网上推荐的辅助工具很多&#xff0c;此篇文章…

Reflexion:通过语言反馈增强的智能体

Reflexion: Language Agents with Verbal Reinforcement Learning Reflexion: language agents with verbal reinforcement learninghttps://proceedings.neurips.cc/paper_files/paper/2023/hash/1b44b878bb782e6954cd888628510e90-Abstract-Conference.html 1.概述 最近,Re…

慧翰股份毛利率下滑:股权转让纠纷引关注,研发费用率远弱同行还买楼?

《港湾商业观察》施子夫 6月11日&#xff0c;慧翰微电子股份有限公司&#xff08;以下简称&#xff0c;慧翰股份&#xff09;IPO注册申请获证监会同意&#xff0c;预计公司将很快登陆深交所创业板&#xff0c;保荐机构为广发证券。 从业绩面来看&#xff0c;过去三年&#xf…

Python tkinter: 开发一个目标检测GUI小程序

程序提供了一个用户友好的界面&#xff0c;允许用户选择图片或文件夹&#xff0c;使用行人检测模型进行处理&#xff0c;并在GUI中显示检测结果。用户可以通过点击画布上的检测结果来获取更多信息&#xff0c;并使用键盘快捷键来浏览不同的图片。 一. 基本功能介绍 界面布局&am…

大厂10余年经验总结,用户研究领域入门标准书籍来了!

《用户研究方法&#xff1a;卓越产品和服务的用户研究技巧》一书近期出版&#xff0c;本书是用户研究领域入门标准书籍&#xff0c;是一本带你进入用户研究世界&#xff0c;通过研究用户让您工作更出色的书籍。 内容及特色 本书共 10 章&#xff0c;分为三篇。 第一篇&#xf…

Web攻防基础篇-文件上传漏洞

文件解析安全问题上&#xff0c;格式解析是一对一的&#xff08;不能jpg解析php&#xff09;&#xff0c;换句话来说有解析错误配置或后缀解析漏洞时才能实现格式差异解析。 文件上传漏洞 程序或系统未对上传文件作全面的限制&#xff0c;导致用户可以上传某些非法文件&#…

景区气象站:旅游安全与舒适体验的守护者

在旅游行业蓬勃发展的今天&#xff0c;越来越多的游客选择走出家门&#xff0c;探索世界的每一个角落。然而&#xff0c;旅游不仅仅是欣赏美景、体验文化&#xff0c;更是对未知的探索和对安全的追求。在这一过程中&#xff0c;景区气象站作为旅游安全与舒适体验的守护者&#…

秋招突击——6/30——{爬楼梯、杨辉三角、打家劫舍、完全平方数}

文章目录 引言新作爬楼梯个人实现参考实现 杨辉三角个人实现参考实现 打家劫舍个人实现参考实现 完全平方数个人实现参考实现 总结 引言 回家以来&#xff0c;和朋友的聚会暂时告一段落了&#xff0c;后面就准备闭关&#xff0c;继续准备秋招了&#xff0c;不能在浪费时间了。…

windosw下宝塔面板mysql无法使用的问题

先了解一下什么是wsl1和wsl2 WSL 1:WSL 1 使用的是一个兼容层,通过翻译 Linux 系统调用,使其能够在 Windows 内核上运行。这种方法的性能较好,但并不能完全兼容所有的 Linux 功能。WSL 2:WSL 2 通过使用真正的 Linux 内核在轻量级虚拟机 (VM) 中运行 Linux,这使得它能更好…

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

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

3.js - premultiplyAlpha

你瞅啥啊&#xff01;&#xff01;&#xff01; 先看效果图吧 代码 // ts-nocheck // 引入three.js import * as THREE from three // 导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls // 导入lil.gui import { GUI } from three/ex…

qt打包生成的.exe 桌面快捷键图标模糊/有锯齿

图标使用的是ico文件,如果你的ico里面只有一个尺寸的.png图片,那么qt打包好的exe快捷键图标就会模糊/有锯齿 1.IcoFX图标编辑v3.8.0 便携版 下载地址: http://www.xz7.com/downinfo/547373.html 这个支持中文 2.准备一个256x256的png图标 3.操作流程 然后另存为ico格式即可