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 是 nilif 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 int64if blockprofilerate > 0 {t0 = cputicks()}// 锁住 channel,并发安全lock(&c.lock)// 如果 channel 关闭了if c.closed != 0 {// 解锁unlock(&c.lock)// 直接 panicpanic(plainError("send on closed channel"))}// 如果接收队列里有 goroutine,直接将要发送的数据拷贝到接收 goroutineif 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 处拷贝到 qptypedmemmove(c.elemtype, qp, ep)// 发送游标值加 1c.sendx++// 如果发送游标值等于容量值,游标值归 0if 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 = 0if t0 != 0 {mysg.releasetime = -1}mysg.elem = epmysg.waitlink = nilmysg.g = gpmysg.selectdone = nilmysg.c = cgp.waiting = mysggp.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 = nilif gp.param == nil {if c.closed == 0 {throw("chansend: spurious wakeup")}// 被唤醒后,channel 关闭了。坑爹啊,panicpanic(plainError("send on closed channel"))}gp.param = nilif mysg.releasetime > 0 {blockevent(mysg.releasetime-t0, 2)}// 释放当前 goroutine 的 sudogmysg.c = nilreleaseSudog(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 上绑定的 goroutinegp := 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.elemtypeBitsBulkBarrier(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.mzph.cn/pingmian/38601.shtml

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

相关文章

error: Sandbox: rsync.samba in Xcode project

在Targets 的 Build Settings 搜索&#xff1a;User script sandboxing 设置为NO

python课程设计作业-TCP客户端-服务端通信

说明文档 目录 小组成员分工 作品功能介绍 使用的工具和方法 设计的步骤 课程设计中遇到的问题 结论 1. 小组成员分工 本次课程设计由以下小组成员完成&#xff1a; xxx 2. 作品功能介绍 本次课程设计的作品是一个简单的基于 TCP 协议的客户端-服务端通信示例。通过这个示…

【SpringBoot Web框架实战教程】06 SpringBoot 整合 Druid

不积跬步&#xff0c;无以至千里&#xff1b;不积小流&#xff0c;无以成江海。大家好&#xff0c;我是闲鹤&#xff0c;微信&#xff1a;xxh_1459&#xff0c;十多年开发、架构经验&#xff0c;先后在华为、迅雷服役过&#xff0c;也在高校从事教学3年&#xff1b;目前已创业了…

阿里云centos7.9 挂载数据盘到 www目录

一、让系统显示中文 参考&#xff1a;centos7 怎么让命令行显示中文&#xff08;英文-&#xff1e;中文&#xff09;_如何在命令行中显示中文-CSDN博客 1、输入命令&#xff1a;locale -a |grep "zh_CN" 可以看到已经存在了中文包 2、输入命令&#xff1a;sudo vi…

AGPT•intelligence:带你领略全新量化交易的风采

随着金融科技的快速发展&#xff0c;量化交易已经成为了投资领域的热门话题。越来越多的投资者开始关注和使用量化交易软件来进行投资决策。在市场上有许多量化交易软件可供选择。 Delaek&#xff0c;是一位资深的金融科技专家&#xff0c;在 2020年成立一家专注于数字资产量化…

第一后裔延迟高怎么办?快速降低第一后裔延迟

第一后裔/The First Descendant一款射击游戏&#xff0c;融合了刷宝、角色扮演、团队合作、剧情等元素&#xff0c;让每个玩家都能在自己的角度上&#xff0c;找到切入点&#xff0c;并不断地成长&#xff0c;一步步解开后裔身上隐藏的秘密。近期该作正式上线&#xff0c;很多玩…

vue项目创建+eslint+Prettier+git提交规范(commitizen+hooks+husk)

# 步骤 1、使用 vue-cli 创建项目 这一小节我们需要创建一个 vue3 的项目&#xff0c;而创建项目的方式依然是通过 vue-cli 进行创建。 不过这里有一点大家需要注意&#xff0c;因为我们需要使用最新的模板&#xff0c;所以请保证你的 vue-cli 的版本在 4.5.13 以上&#xff…

Debian linux忘记root密码如何重置

重启电脑, 到下图再按 e 键 在页面中可以看到有个ro的行&#xff0c;在ro行的尾部&#xff0c;添加 rw init/bin/bas 3. ctrl X 启动系统&#xff0c;最后会进入命令行模式 4. 重设root密码&#xff0c;输入命令 passwd root&#xff0c;按照提示输入新密码并确认 5. 重启系…

基于Python的自动化测试框架-Pytest总结-第一弹基础

Pytest总结第一弹基础 入门知识点安装pytest运行pytest测试用例发现规则执行方式命令行执行参数 配置发现规则 如何编写测试Case基础案例断言语句的使用pytest.fail() 和 Exceptions自定义断言函数异常测试测试类形式 pytest的Fixture使用Fixture入门案例使用fixture的Setup、T…

昇思25天学习打卡营第8天|模型训练

昇思25天学习打卡营第8天|模型训练 前言模型训练构建数据集定义神经网络模型定义超参、损失函数和优化器超参损失函数优化器 训练与评估 个人任务打卡&#xff08;读者请忽略&#xff09;个人理解与总结 前言 非常感谢华为昇思大模型平台和CSDN邀请体验昇思大模型&#xff01;从…

linux中如何启动python虚拟环境

找到python虚拟环境所在目录 执行下面的命令即可 source auth_python/bin/activate

【遇坑笔记】Node.js 开发环境与配置 Visual Studio Code

【遇坑笔记】Node.js 开发环境与配置 Visual Studio Code 前言node.js开发环境配置解决pnpm 不是内部或外部命令的问题&#xff08;pnpm安装教程&#xff09; 解决 pnpm : 无法加载文件 C:\Program Files\nodejs\pnpm.ps1&#xff0c;因为在此系统上禁止运行脚本。 前言 最近部…

【代码随想录】【算法训练营】【第49天】 [300]最长递增子序列 [674]最长连续递增序列 [718]最长重复子数组

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 49&#xff0c;周二&#xff0c;坚持不了一点~ 题目详情 [300] 最长递增子序列 题目描述 300 最长递增子序列 解题思路 前提&#xff1a;最大递增子序列的长度 思路&#xff1a;动态规划 d…

基于X86+FPGA的精密加工检测设备解决方案

应用场景 随着我国高新技术的发展和国防现代化发展&#xff0c;航空、航天等领域需 要的大型光电子器件&#xff0c;微型电子机械、 光 电信息等领域需要的微型器件&#xff0c;还有一些复杂零件的加工需求日益增加&#xff0c;这些都需要借助精密甚至超精密的加工检测设备 客…

esp12实现的网络时钟校准

网络时间的获取是通过向第三方服务器发送GET请求获取并解析出来的。 在本篇博客中&#xff0c;网络时间的获取是一种自动的行为&#xff0c;当系统成功连接WiFi获取到网络天气后&#xff0c;系统将自动获取并解析得到时间和日期&#xff0c;为了减少误差每两分钟左右进行一次校…

web平台—apache

web平台—apache 1. 学apache前需要知道的知识点2. apache详解2.1 概述2.2 工作模式2.3 启动apache网站整体流程2.4 相关文件保存位置2.5 配置文件详解 3. apache配置实验实验1&#xff1a;设置apache的目录别名实验2&#xff1a;apache的用户认证实验3&#xff1a;虚拟主机 (重…

江门MES制造执行系统:助力工厂实现智能化管理

江门MES制造执行系统(MES)在工厂实现智能化管理方面发挥着重要作用&#xff0c;以下是它的一些助力方面&#xff1a; 实时监控与控制&#xff1a;江门MES系统可以实时监控生产过程中的各个环节&#xff0c;包括设备状态、生产进度、质量指标等&#xff0c;帮助工厂管理人员及时…

LW-DETR: A Transformer Replacement to YOLO for Real-Time Detection

LW-DETR: A Transformer Replacement to YOLO for Real-Time Detection 论文链接&#xff1a;http://arxiv.org/abs/2406.03459 代码链接&#xff1a;https://github.com/Atten4Vis/LW-DETR 一、摘要 介绍了一种轻量级检测变换器LWDETR&#xff0c;它在实时物体检测方面超越…

CF1981D Turtle and Multiplication 题解

Turtle and Multiplication 传送门 Turtle just learned how to multiply two integers in his math class, and he was very excited. Then Piggy gave him an integer n n n , and asked him to construct a sequence a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1​,…

Java [ 基础 ] Stream流 ✨

✨探索Java基础Stream流✨ 在现代Java编程中&#xff0c;Stream是一个非常强大的工具&#xff0c;它提供了一种更高效和简洁的方式来处理集合数据。在这篇博客中&#xff0c;我们将深入探讨Java中的Stream流&#xff0c;介绍它的基础知识、常见操作和一些实用示例。 什么是Str…