Go:关于 Channel

news2024/9/29 7:18:26

文章目录

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

写在前面

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

内容

模型图与代码

我们先来看下 Channel 的模型图:
在这里插入图片描述
以上的图是一个简要的模型图,意味着丢失一些细节,我们再结合源码来看下:

type hchan struct {
    // 以下是环形缓冲区
	qcount   uint           // total data in the queue
	dataqsiz uint           // size of the circular queue
	buf      unsafe.Pointer // points to an array of dataqsiz elements
	elemsize uint16
	elemtype *_type // element type
	// Channel 的关闭状态
	closed   uint32
	// 以下是等待队列和接收队列
	sendx    uint   // send index
	recvx    uint   // receive index
	recvq    waitq  // list of recv waiters
	sendq    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)

    // 向一个已经关闭的通道发送数据,会 panic
	if 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 = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	mysg.elem = ep
	mysg.waitlink = nil
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.waiting = mysg
	gp.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 = nil
	gp.activeStackChans = false
	closed := !mysg.success
	gp.param = nil
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	mysg.c = nil
	releaseSudog(mysg)
	// 再检查一下通道是否关闭了,向一个已经关闭的通道发送数据,会 panic
	if 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 queue
		qp := 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 = 0
	if t0 != 0 {
		mysg.releasetime = -1
	}
	
	mysg.elem = ep
	mysg.waitlink = nil
	gp.waiting = mysg
	mysg.g = gp
	mysg.isSelect = false
	mysg.c = c
	gp.param = nil
	c.recvq.enqueue(mysg)
	
	atomic.Store8(&gp.parkingOnChan, 1)
	gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)

	// someone woke us up
	if mysg != gp.waiting {
		throw("G waiting list is corrupted")
	}
	gp.waiting = nil
	gp.activeStackChans = false
	if mysg.releasetime > 0 {
		blockevent(mysg.releasetime-t0, 2)
	}
	success := mysg.success
	gp.param = nil
	mysg.c = nil
	releaseSudog(mysg)
	return true, success
}

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

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

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

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

相关文章

【Spring Boot】SpringBoot 单元测试

SpringBoot 单元测试 一. 什么是单元测试二. 单元测试的好处三. Spring Boot 单元测试单元测试的实现步骤 一. 什么是单元测试 单元测试&#xff08;unit testing&#xff09;&#xff0c;是指对软件中的最⼩可测试单元进⾏检查和验证的过程就叫单元测试。 二. 单元测试的好处…

TDengine时序数据库学习使用

数据库要求&#xff1a; 1.目前服务器只能在linux运行&#xff0c;先安装服务器版本v1 2.下载与v1完全匹配的客户端版本v1&#xff08;客户端与服务器的版本号不匹配可能访问不了服务器&#xff09; 第一步 安装 安装服务器注意&#xff0c;安装教程&#xff1a; 使用安装…

vivado FFT IP仿真(3)FFT IP选项说明

xilinx FFT IP手册PG109 1 Configuration 2 Implementation 3 Detailed Implementation IP Symbol

Android源码下载

文章目录 一、Android源码下载 一、Android源码下载 AOSP 是 Android Open Source Project 的缩写。 git 常用命令总结 git 远程仓库相关的操作 # 查看 remote.origin.url 配置项的值 git config --list Android9.0之前代码在线查看地址&#xff1a;http://androidxref.com/ …

关于webWorker未解问题

今天尝试学习webworker,尝试在vue3项目里面使用 使用的就是常规方法,使用worker-loader,加上在vue.config.js内部添加配置 使用完发现问题 如图所见,该worker仅仅配置点击后传输字符串"1",并在worker内部打印,发现打印不出来 但是仅仅只是将引入的文件换个名字 …

什么是兼容性测试? 有哪些方法?

在现今数字化世界中&#xff0c;软件和应用程序的多样性和复杂性已经达到了前所未有的高度。不同的操作系统、浏览器、设备和网络环境使得开发人员面临着严峻的挑战&#xff0c;即如何确保他们的软件在各种不同条件下都能正常运行。这就是兼容性测试的重要性所在。 一、什么是兼…

十一、2023.10.5.计算机网络(end).11

文章目录 17、说说 TCP 可靠性保证&#xff1f;18、简述 TCP 滑动窗口以及重传机制?19、说说滑动窗口过小怎么办?20、说说如果三次握手时候每次握手信息对方没收到会怎么样&#xff0c;分情况介绍&#xff1f;21、简述 TCP 的 TIME_WAIT&#xff0c;为什么需要有这个状态&…

2579. 统计染色格子数(javascript)

有一个无穷大的二维网格图&#xff0c;一开始所有格子都未染色。给你一个正整数 n &#xff0c;表示你需要执行以下步骤 n 分钟&#xff1a; 第一分钟&#xff0c;将 任一格子染成蓝色。之后的每一分钟&#xff0c;将与蓝色格子相邻的 所有 未染色格子染成蓝色。 下图分别是 …

Maven下载与文件配置

文章目录 官网下载相应文件修改配置文件设置环境变量 官网下载相应文件 https://maven.apache.org/ 点击Download ,找到Files 下载好了&#xff0c;请解压&#xff0c;放在你需要存储的位置&#xff01; 修改配置文件 打开解压的文件&#xff1a; 在conf 下有一个setting…

NCNN:备忘

1&#xff1a;NCNN中Mat与CV中Mat的区别 ncnn中Mat数据的排列格式为(channel, h, w)&#xff1a;bbbb....gggg.....rrrr opencv中Mat中数据的排列格式为(h, w, channel)&#xff1a;bgrbgrbgrbgr... 2&#xff1a;NCNN::Mat的内存排布&#xff08;图解&#xff09;力荐 参考…

Unity实现设计模式——模板方法模式

Unity实现设计模式——模板方法模式 模板模式(Template Pattern)&#xff0c; 指在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现&#xff0c;但调用将以抽象类中定义的方式进行。 简单说&#xff0c; 模板方法模式定义一个操作中的算法的骨架&…

【机器学习 | 回归问题】超越直线:释放多项式回归的潜力 —— 详解线性回归与非线性 (含详细案例、源码)

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

对自动化测试的一些见解

1、手工测试和自动化测试用例 手工测试用例是针对功能测试人员的&#xff0c;而自动化测试用例是针对自动化测试用例框架或工具的。 1&#xff09;手工测试用例特点 较好的异常处理能力&#xff0c;能通过人为的逻辑判断校验当前步骤是否正确实现&#xff1b; 人工执行用例具有…

startsWith()方法的使用

startsWith()方法一般用于检测某请求字符串是否以指定的前缀开始的。 例如&#xff1a;服务器要判断某个请求是否合规&#xff0c;首先确定协议&#xff0c;比如http、ftp等&#xff0c;这时&#xff0c;就可以用startsWith()。 startsWith 是则返回true&#xff0c;否则返回…

css自学框架之面板

面板是我们开发中经常用到&#xff0c;也就是页面版面中一块一块的板块&#xff0c;效果如下图&#xff1a; 一、css代码 .myth-panel {background-color: var(--white);border: solid 1px transparent;}.myth-panel .myth-panel-header {border-bottom: solid 1px transpar…

一种重要的天然氨基酸L-Homopropargylglycine(HPG)|CAS:942518-19-6

产品简介&#xff1a;L-Homopropargylglycine是一种重要的天然氨基酸&#xff0c;具有多种生物活性和医学应用价值。它广泛应用于生物学、药学、化学等多个领域。在生物学中&#xff0c;HPG被用作蛋白质合成的标记物&#xff0c;可以通过其特殊的化学反应与蛋白质中的半胱氨酸残…

使用HHDBCS管理MongoDB

1 连接MongoDB 打开HHDBCS&#xff0c;在数据库类型中选择mongodb&#xff0c;填入相关信息&#xff0c;点击“登陆”即可。 也可以使用SSH通道进行登陆。 2 命令窗口 点击命令窗口&#xff0c;可以对数据库发出指令。 可以根据个人习惯&#xff0c;对命令窗口进行设置…

基于springboot实现自习室预订系统的设计与实现项目【项目源码+论文说明】

基于springboot实现自习室预订系统的设计与实现演示 摘要 在网络高速发展的时代&#xff0c;众多的软件被开发出来&#xff0c;给学生带来了很大的选择余地&#xff0c;而且人们越来越追求更个性的需求。在这种时代背景下&#xff0c;学院只能以学生为导向&#xff0c;所以自习…

verdi显示OVM/UVM Hierarchy View

verdi显示OVM/UVM Hierarchy View 背景 使用vcsverdiUVM进行UVM debug的时候&#xff0c;verdi加载的时候看不到UVM树形结构图 解决办法 simv UVM_VERDI_TRACE“UVM_AWAREHIER” -guiverdi 2023-10-9 打开界面后&#xff0c;并不会直接显示树形层级 需要先仿真一定时间&#x…

工资「喂饱肚子」,副业「养活灵魂」!职场人的生存之道

文章目录 工资&#xff1a;生计的基础1. 收入局限性2. 缺乏多样性3. 有限的时间投入 副业&#xff1a;充实生活的机会1. 增加收入2. 提升技能3. 追求兴趣4. 增强创造力5. 实现梦想 如何找到适合的副业&#xff1f;1. 确定兴趣和技能2. 市场需求3. 时间和资源4. 资金投入5. 建立…