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;很多玩…

获取微信公众号所有订阅用户,并批量获取用户基本信息

获取微信公众号所有订阅用户&#xff0c;并批量获取用户基本信息 public void syncSubscribe() {String appId ApiConstants.PUBLIC_ACCOUNT_APP_ID;// 所有订阅用户List<String> openIdList getUserList(appId);// 一个批次只能查100条数据&#xff0c;将List按100的长…

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

2024自然语言处理期末回忆

2024.6.19考 总体来说&#xff0c;很离谱&#xff0c;那么多ppt&#xff0c;考之前以为肯定会考算法&#xff0c;看了好久的算法&#xff0c;结果考了很多概念题。 就是说&#xff0c;这门课很抽象&#xff0c;能不选就别选了。 1.&#xff08;1&#xff09;什么是预训练词向…

【遇坑笔记】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;为了减少误差每两分钟左右进行一次校…

clock时钟周期

clock时钟周期 #include <iostream> #include <ctime>int main() {clock_t start clock(); // 获取开始时间的时钟周期数// 模拟一些工作for (long long i 0; i < 10000000000; i);clock_t end clock(); // 获取结束时间的时钟周期数std::cout << &q…

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;帮助工厂管理人员及时…