golang chan 探究

前言

之前在看golang多线程通信的时候, 看到了go 的管道. 当时就觉得这玩意很神奇, 因为之前接触过的不管是phpjavaPythonjsc等等, 都没有这玩意, 第一次见面, 难免勾起我的好奇心. 所以就想着看一看它具体是什么东西. 很明显, 管道是go实现在语言层面的功能, 所以我以为需要去翻他的源码了. 虽然最终没有翻到C的层次, 不过还是受益匪浅.

见真身

结构体

要想知道他是什么东西, 没什么比直接看他的定义更加直接的了. 但是其定义在哪里么? 去哪里找呢? 还记得我们是如何创建chan的么? make方法. 但是当我找过去的时候, 发现make方法只是一个函数的声明.

这, 还是没有函数的具体实现啊. 汇编看一下. 编写以下内容:

package mainfunc main() {_ = make(chan int)
}

执行命令:

go tool compile -N -l -S main.go

虽然汇编咱看不懂, 但是其中有一行还是引起了我的注意.

make调用了runtime.makechan. 漂亮, 就找他.

找到他了, 是hchan指针对象. 整理了一下对象的字段(不过人家自己也有注释的):

// 其内部维护了一个循环队列(数组), 用于管理发送与接收的缓存数据. 
type hchan struct {// 队列中元素个数qcount   uint// 队列的大小(数组长度)dataqsiz uint// 指向底层的缓存队列, 是一个可以指向任意类型的指针. buf      unsafe.Pointer// 管道每个元素的大小elemsize uint16// 是否被关闭了closed   uint32// 管道的元素类型elemtype *_type// 当前可以发送的元素索引(队尾)sendx    uint  // 当前可以接收的元素索引(队首)recvx    uint  // 当前等待接收数据的 goroutine 队列recvq    waitq// 当前等待发送数据的 goroutine 队列sendq    waitq // 锁, 用来保证管道的每个操作都是原子性的. lock mutex
}

可以看的出来, 管道简单说就是一个队列加一把锁.

发送数据

依旧使用刚才的方法分析, 发送数据时调用了runtime.chansend1 函数. 其实现简单易懂:

然后查看真正实现, 函数步骤如下(个人理解, 有一些 test 使用的代码被我删掉了. ):

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {// 异常处理, 若管道指针为空if c == nil {if !block {return false}gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)throw("unreachable")}// 常量判断, 恒为 false, 应该是开发时调试用的. if debugChan {print("chansend: chan=", c, "\n")}// 常量, 恒为 false, 没看懂这个判断if raceenabled {racereadpc(c.raceaddr(), callerpc, funcPC(chansend))}// 若当前操作不阻塞, 且管道还没有关闭时判断// 当前队列容量为0且没有等待接收数据的 或 当前队列容量不为0且队列已满// 那么问题来了, 什么时候不加锁呢? select 的时候. 可以在不阻塞的时候快速返回if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||(c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {return false}// 上锁, 保证操作的原子性lock(&c.lock)// 若管道已经关闭, 报错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 {// 取出对尾的地址qp := chanbuf(c, c.sendx)// 将ep 的内容拷贝到 ap 地址typedmemmove(c.elemtype, qp, ep)// 更新队尾索引c.sendx++if c.sendx == c.dataqsiz {c.sendx = 0}c.qcount++unlock(&c.lock)return true}// 若当前不阻塞, 直接返回if !block {unlock(&c.lock)return false}// 当走到这里, 说明数据没有成功发送, 且需要阻塞等待. // 以下代码没看懂, 不过可以肯定的是, 其操作为阻塞当前协程, 等待发送数据gp := getg()mysg := acquireSudog()mysg.releasetime = 0if t0 != 0 {mysg.releasetime = -1}mysg.elem = epmysg.waitlink = nilmysg.g = gpmysg.isSelect = falsemysg.c = cgp.waiting = mysggp.param = nilc.sendq.enqueue(mysg)gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)KeepAlive(ep)if mysg != gp.waiting {throw("G waiting list is corrupted")}gp.waiting = nilgp.activeStackChans = falseif gp.param == nil {if c.closed == 0 {throw("chansend: spurious wakeup")}panic(plainError("send on closed channel"))}gp.param = nilif mysg.releasetime > 0 {blockevent(mysg.releasetime-t0, 2)}mysg.c = nilreleaseSudog(mysg)return true
}

虽然最终阻塞的地方没看太明白, 不过发送数据的大体流程很清楚:

  1. 若无需阻塞且不能发送数据, 返回失败
  2. 若存在接收者, 直接发送数据
  3. 若存在缓存, 将数据放到缓存中
  4. 若无需阻塞, 返回失败
  5. 阻塞等待发送数据

其中不加锁的操作, 在看到selectnbsend函数的注释时如下:

// compiler implements
//
//	select {
//	case c <- v:
//		... foo
//	default:
//		... bar
//	}
//
// as
//
//	if selectnbsend(c, v) {
//		... foo
//	} else {
//		... bar
//	}
//
func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {return chansend(c, elem, false, getcallerpc())
}

看这意思, select关键字有点类似于语法糖, 其内部会转换成调用selectnbsend函数的简单if判断.

接收数据

至于接收数据的方法, 其内部实现与发送大同小异. runtime.chanrecv 方法.

源码简单看了一下, 虽理解不深, 但对channel也有了大体的认识.

上手

简单对channel的使用总结一下.

定义

// 创建普通的管道类型, 非缓冲
a := make(chan int)
// 创建缓冲区大小为10的管道
b := make(chan int, 10)
// 创建只用来发送的管道
c := make(chan<- int)
// 创建只用来接收的管道
d := make(<-chan int)
// eg: 只用来接收的管道, 每秒一个
e := time.After(time.Second)

发送与接收

// 接收数据
a := <- ch
b, ok := <- ch
// 发送数据
ch <- 2

最后, 看了一圈, 感觉channel并不是很复杂, 就是一个队列, 一端接受, 一端发送. 不过其对多协程处理做了很多优化. 与协程配合, 灵活使用的话, 应该会有不错的效果.

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

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

相关文章

计算机网络-信道复用技术

还记得计算机网络中的信道复用技术么? 来来来, 一起复习一下. why 问: 什么是信道复用. 在回答这个问题之前先看这样一个场景: 其中u1 u2是两个用户, 如果这两个用户之间连通的信道在他们使用过程中, 被他们完全占用了, 其他人就只能等着了. 那有人说了, 那就多架设信道不就…

IP 数据报首部分析

来来来, 爷们. 不是一直说纸上得来终觉浅么. 今咱就抓个数据报具体看一看真实网络中的 IP 报首部. 操作方法很简单, 使用wireshark进行抓包. 抓包后随便找个包看一下就行, 毕竟所有通信的包都需要经过网络层.(同时, wireshark会对协议的相关信息给出标识, 更方便我们查看) 其中…

git 子模块在项目中的使用

在公司的项目中, 经常会遇到一些公共的内容, 多个项目中间通用的, 不可能每次都将整个代码复制一遍, 遇到这种情况有很多不同的解决方案, 一般来说, 项目是通过 git 来管理的, 巧了, git 也同样支持子模块. 创建子模块 git submodule add gitgitee.com:hujingnb/submodule_so…

GO 内存对齐

前言 之前遇到过这样一个情况(发现问题的结构体并不长这样, 不过为了引出问题, 改了一下): type Test struct { b bool i3 int32 i8 int8 i64 int64 by byte } func main() { t : Test{} fmt.Printf("%d", unsafe.Sizeof(t)) } 创建一个结构体, 查看一下其内存占用.…

HBase 命令行

hbase是一款分布式数据库. 其对数据的索引只通过row key进行. 在存储数据的时候, 通过row key的排序进行存储. 在面对一个新的数据库时, 深究其原理并不知一个明智的选择, 正如开车一般, 大多数人都是先学会开车, 然后在开车的过程中车子出故障了, 再慢慢学着去修理. 不管怎么说…

《论可计算数及其在判定上的应用》简单理解

刚刚拜读了一本书, 《图灵的秘密》. 该书介绍了图灵的论文《论可计算数及其在判定上的应用》, 其指出: 一个拥有铅笔, 纸和一串明确指令的人类计算者, 可以被看做是一种图灵机. 那么图灵机是什么呢? 是图灵为了描述可计算数而引出的一个虚构的可以做一些简单操作的计算机器. 尽…

PHP为什么empty可以访问不存在的索引

开始之前, 先抛出问题: $arr []; echo empty: , PHP_EOL; var_dump(empty($arr[1])); echo is_array: , PHP_EOL; var_dump(is_array($arr[1]));这段代码的运行结果: 你是否和我有过同样的疑问? 同样是函数, 为什么empty访问不存在的索引就不会报错呢? 按理说哈, 函数调用的…

浮点数运算丢失精度

今天碰到了这样一个情况, 使我又去翻阅了原来课本, 在Pthon中如果输入下面这段程序: print(sys.float_info.max - 1.0) print(sys.float_info.max)结果如下: 结果发现, 这数字根本没有变化. 本来这没什么, 看这数字, 10的308次方, 也就是说, 减去的1是在308位之后了, 这里没有…

变量的作用域

起因 最近闲来无事, 在 Python 官网上看到了2.0版本, 是2001年的. 打算装起来体验一下最初发布的版本, 但是发现只有 Windows 版本, 所以我就装了个 Windows10的虚拟机, 就在我打算安装的时候, 发现: 这激起了我的好胜欲, 于是我就依次安装了Windows 8, Windows 7, Windows XP…

PHP8的注解

PHP8.0增加了注解的支持, 虽然 PHP的注解没用过, 但是咱用过JAVA的注解呀. 注解这玩意怎么用? 简单说就下面几步: 定义注解类使用注解提取注解 到了PHP中, 也基本上换汤不换药. 使用 定义注解类 #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION)] cl…

HBase 数据存储结构

在HBase中, 从逻辑上来讲数据大概就长这样: 单从图中的逻辑模型来看, HBase 和 MySQL 的区别就是: 将不同的列归属与同一个列族下支持多版本数据 这看着感觉也没有那么太大的区别呀, 它解决了 MySQL 的那些问题呢? 每一个新事物的出现, 都是为了解决原本存在的问题. 对写入…

spark计算操作整理

spark 的计算流程大概如图: 其中, 通过多次处理, 生成多个中间数据, 最后对结果进行操作获得数据. 本文不涉及任何原理, 仅总结spark在处理的时候支持的所有操作, 方便后面使用的时候, 可以参照本文进行数据的处理. 以下函数整理, 基与Python中RDD对象. 数据的转换操作 数据…

软件工程模型

你在工作中, 软件的开发流程是怎样的? 你是否想过, 除了你当前使用的流程, 还存在其他怎样的流程? 现在的流程有哪些问题, 又能够如何解决? 别说, 前辈们已经给出了一些项目流程的模型, 既软件工程. 可以简单了解一下, 带动一下我这生了锈的脑子. 在很久以前, 一个软件的从…

路径.git下的文件

用了这么久的git, 可以毫不谦虚地说对git是一无所知. 每天用来用去的就是commit, add, merge 等几个有限的命令, 这不符合我这刨根问底的性格啊. 不行, 得研究研究, 从哪里下手呢? 别的咱先不说, 所有 git 项目都有这么一个文件夹.git, 不如就从它入手 ? 那咱就看看这个文件夹…

git 操作二进制文件

平常用git进行项目管理已经稀松平常了, 今天咱来点不一样的. 平常管理的都是普通的文本文件, 如果是二进制文件, git能够处理么? 比如word文档. 测试一下. 新建一个项目, 在其中创建test1.docx, test2.txt两个空文件并提交. 之后编辑文件并添加标题, git diff看一下效果: 效…

搭建个人博客

一直都想着搞一个自己的个人博客, 拖着这么久, 最近终于开始动手了. 故留下一篇完整的记录, 若你也刚好有相同的需要, 那这篇文章应该恰好能够帮助到你. 准备 云服务器个人域名 如果没有云服务器, 将应用跑在自己的电脑上, 通过内网穿透大概也能达到效果, 但是个人电脑实在没…

nginx 配置文件的匹配规则

引出 之前在对php-fpm 进行nginx代理时, 为了对后台限定 IP 访问, 添加了如下配置: location ^~ /admin {allow 127.0.0.1;deny all; }结果呢? 所有admin路径下的php文件, 全都没有解析, 变成文件下载了. 当时我不知道是什么问题, 不过将这段配置去掉之后, 问题就消失了. 所…

计算机是如何进行时间同步的

WHY 在网络世界中, 各个计算机之间要想协同工作, 时间同步是一个十分重要的基础. 在计算机内部是有自己的时间的, 这个时间通过内部的晶体振荡器差生的固定频率, 来模拟时间流逝进行计算. 虽然频率十分稳定, 但也是有误差的, 虽然现在的工艺水平误差已经十分小了. (关于震荡的…

WordPress架构简单剖析

前言 最近在搭建自己的博客站点时, 选择了网站使用较多的WordPress, 随着慢慢的使用, 它灵活的插件和主题令我折服. 基本上任何想要实现的功能, 都可以在上面通过插件的形式进行添加. 无论是在访问前的缓存、访问后的统计、访问中的过滤、各种流程的修改等等, 几乎都能够以插件…

阿里云定时任务并自动释放

前言 最近写了一个爬虫脚本, 脚本跑在一台北京的 ecs 上. 但奈何因某种未知力量, 需要连接代理才能访问目标网站. 本来想着自己搭代理, 但是太贵了, 就暂时搁置了. 直到我发现了这个: 阿里云香港的服务器, 一个小时才5分钱. 如果脚本直接跑在香港服务器上不就可以了咩, 按照这…