专栏首页吴亲强的深夜食堂彻底搞懂channel原理(三)
原创

彻底搞懂channel原理(三)

上一篇文章主要通过一个现实例子间接反映channel的一些原理。最后一篇开始介绍一些细节,会涉及到源码。

还是从一个简单的代码程序看起。

我们创建了一个无缓冲channel,然后往这个channel发送数据。因为程序中没有读操作ready,所以发送的时候会阻塞。我们通过汇编代码看它底层的调用。

从图中我们看到,上述发送操作,程序运行时实际调用的runtime.chansend1

最终chansend1最终调用的还是chansendchansend的第三个参数block是个bool值,表示操作channel不能立即成功时是否需要阻塞。

具体哪些操作?

  • 向无缓冲channel发送数据且当前无接收者ready
  • 接收无缓冲channel数据且当前无发送者ready
  • 缓冲channel已满,往channel发送数据。
  • 缓冲channel为空,接收channel数据
  • 向一个nilchannel发送数据。(注意,向一个nilchannel发送数据并不会引发panic)。
  • 向一个nilchannel接收数据。

碰到上面的操作,如果不是特殊处理,我们的应用程序会被阻塞,直到被唤醒。

当然对于向nilchannel发送|接收数据,后续再也没机会被唤醒了。

那么如果是快速试错的场景,是不是只要把block改成false,在失败的场景下就不会被阻塞了。

编译这段代码。

可以看出,上面这段代码编译后调用selectnbsend最终发送动作调用的还是chansned,只是传入的blockfalse。这样一旦操作失败,程序不会被阻塞。

同理我们可以得出接收的调用动作。

到这里我们已经知道,

发送数据,最终调用的runtime.chansend

接收数据,最终调用的runtime.chanrecv

接下来我们来说明这两个函数底层是如何操作的。

我们还是以一个无缓冲的channel和缓冲channel来说明。

来看一段简单的程序。

值得一提的是,在Go中使用 go func的时候,本质上调用的是runtime.newproc创建一个g,然后把这个g交给调度器调度。至于什么时候g被调度,然后执行你的代码逻辑,那就要看调度器的"心情"了。

所以上面创建的两个g(暂且称为g1和g2),可以看成是我们向调度器提交了两个任务g,我们无法保证哪个g会被先调度器调度执行,因此我们也不确定发送和接收这两个操作,谁会先被执行。

假设g1先被调度器运行,然后执行代码ch<-struct{}{}

如果g2先被调度器运行,然后执行代码<-ch

当然我们也可以把上面的代码换成画成详细的无缓冲队列核心流程图。

缓冲channel发送的时候分为三种情况,想想我们上篇文章快递员送快递场景。

  • 如果快递柜未满,直接把快递放入到快递柜。(对应缓冲区未满,把发送数据拷贝到缓冲区)
  • 如果快递柜满了,那快递员只能在那等待快递柜空了。(对应把当前g封装成sudog,然后把sudog放到等待发送消息队列sendq中,最后挂起当前g)
  • 如果送快递的时候正好客户在那里等,那就直接把快递给他就是了(对应如果发送的时候发现有等待者,直接数据拷贝给他呗)

我们来创建一个例子。

我们创建了一个缓冲区为7的channelbuffer就是用来存储缓冲元素的,它实际上是一个环形数组。为什么是环形的?因为这样就可以达到复用空间的效果。

此时没有发送接收动作,所以qcount为0,发送(sendx)和接收(recvx)的位置都为0。

我们来看上面的第一种情况。缓冲区未满,

这块代码就比较简单了。如果缓冲区未满,那就把当前要发送的数据拷贝到缓冲区的发送位置,然后发送位置sendx+1,然后当然channel个数qcount+1,整个流程就结束了。

如果缓冲满的情况下,封装当前gsudog,把这个sudog入队等待发送队列,最后调用gopark挂起当前g,上面无缓冲的时候有提到。

最后一种情况,发送的时候正好有等待接收消息者,那么就从recvq中拿出最早开始等待的接受者,然后把发送的数据直接拷贝给他。

send整体有两个动作:拷贝数据—–>唤醒等待的recvq

那么对于接收操作呢?

  • 快递柜里有我的快递,那我直接拿就行了。(对应缓冲区有数据,根据读recvx的位置拿数据)
  • 快递柜还没我的快递,但是快递哥打电话说快到了,那我现在楼下转转。(对应缓冲区无数据,把当前g封装成sudog,然后放入到等待接收消息队列recvq中)。
  • 去拿一个快递的时候,正好一个快递员放我另一个快递的时候因为快递柜满了,在那等着。(对应缓冲区满了,且还有等待发送者。此时先到缓冲区获取当前读recvx位置的数据,然后再从等待发送者队列中取出最早等待的发送者,把他要发送的数据拷贝拷贝到当前我读取数据的位置(保证先入先出的顺序),最后更新发送位置和更新位置即可)。

第一种情况就简单了。直接通过当前读位置recvx读取buffer对应的值,这里还需要通过判断是否忽略返回值,而决定需不需要往当前接收操作拷贝数据。然后移动recvx位置,元素个数qcount-- ,最后解锁即可。

第二种情况,封装当前gsudog,把这个sudog入队等待接收队列,最后调用gopark挂起当前g。上面无缓冲的时候画过这个逻辑。

第三种情况有点复杂。

这种情况下,当获取到一个等待发送者,对于接收者来说,如果我们直接拿它的发送数据返回会发生什么?举个例子

上图,channel满了,且sendq有一个等待发送者(假设是G8,发送数据为800),此时执行接收操作,也就出现上述第三种情况。

如果此时我们直接拿G8的数据,那么数据就不能保证先入先出了。

所以正确的操作是,读取当前recvx位置(0)buffer100,然后把G8的数据800拷贝到0的位置,最后把recvq的位置向前移动,同步发送位置sendx等于recvq。这里,可以思考下为啥?

到这里缓冲channel的核心流程就说完了。如图,

另外关注公众号吴亲强的深夜食堂 后台回复channel有我准备的一个小ppt,可以跟着一起看。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 彻底搞懂channel原理(一)

    之前断断续续看过Go几个模块的源码,可从未下笔,导致有些细节记不起来了。打算写一系列文章重新记录。

    吴亲库里
  • 彻底搞懂channel原理(二)

    上一篇文章彻底搞懂channel原理(一)主要介绍channel运行时是通过hchan表示的,也简单说明了hchan各个字段的含义。

    吴亲库里
  • 彻底搞懂MyBatis插件原理及PageHelper原理

    提到插件,相信大家都知道,插件的存在主要是用来改变或者增强原有的功能,MyBatis中也一样。

    公众号 IT老哥
  • 看完让你彻底搞懂Websocket原理

    杭州前端工程师
  • 彻底搞懂NIO效率高的原理

    这篇文章读不懂的没关系,可以先收藏一下。笔者准备介绍完epoll和NIO等知识点,然后写一篇Java网络IO模型的介绍,这样可以使Java网络IO的知识体系更加...

    全菜工程师小辉
  • 手写mybatis彻底搞懂框架原理

    mybatis的前身是iBatis,其源于“Internet”和“abatis”的组合,是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。myb...

    全菜工程师小辉
  • 一文彻底搞懂 HTTPS 的工作原理!

    当你打开浏览器,访问某个网站,如果网址旁有个小锁,代表访问的网址是安全的,反之不安全。当我们没有看到那个小锁的小图标的时候,需要提高警惕,不要随意输入个人重要的...

    杰哥的IT之旅
  • 彻底搞懂彩虹表的实现原理

    7788的术语我就不多说了,简而言之,就是一种破解md5或者sha1这种哈希散列算法的一种办法。

    诺浅
  • 带你彻底搞懂-View的工作原理!

    1.ViewRoot对应ViewRootImpl类,是连接WindowManager和DecorView的纽带。View的三大流程是通过ViewRoot完成的。...

    胡飞洋
  • 彻底搞懂epoll高效运行的原理

    这篇文章读不懂的没关系,可以先收藏一下。笔者准备介绍完epoll和NIO等知识点,然后写一篇Java网络IO模型的介绍,这样可以使Java网络IO的知识体系更加...

    全菜工程师小辉
  • 彻底搞懂Object.defineProperty

    早在大半年前,掘金某位用户分享的面试题整理中有一题,简述let与const区别,你能自己模拟实现它们吗?,题目意思大概如此,时间久远我也很难找到那篇文章,当时看...

    zz_jesse
  • 彻底搞懂 Object.defineProperty

    早在大半年前,掘金某位用户分享的面试题整理中有一题,简述let与const区别,你能自己模拟实现它们吗?,题目意思大概如此,时间久远我也很难找到那篇文章,当时看...

    coder_koala
  • 彻底搞懂ArrayList

    同样是空的数组对象,和EMPTY_ELEMENTDATA区别在于,无参构造函数的空数组会用DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值...

    每天学Java
  • 彻底搞懂Scrapy的中间件(三)

    在前面两篇文章介绍了下载器中间件的使用,这篇文章将会介绍爬虫中间件(Spider Middleware)的使用。

    青南
  • 彻底搞懂Java动态代理

    现在spring大行其道,经常使用的AOP功能就是动态代理机制的实现。动态代理到底是怎么回事呢?

    Criss@陈磊
  • 彻底搞懂Java动态代理

    现在spring大行其道,经常使用的AOP功能就是动态代理机制的实现。动态代理到底是怎么回事呢?

    普通程序员
  • 彻底搞懂 Git-Rebase

    来源:http://jartto.wang/2018/12/11/git-rebase/?hmsr=toutiao.io&utm_medium=toutiao....

    程序猿DD
  • 彻底搞懂HashMap(上)

    相信很多朋友对于HashMap,开发中我们几乎每天都要使用它,但是每当问到map的一些原理时,很多朋友就不知道如何去回答,甚至一问三不知,从而离我们心仪的off...

    用户8870853
  • 彻底搞懂HashMap(上)

    相信很多朋友对于HashMap,开发中我们几乎每天都要使用它,但是每当问到map的一些原理时,很多朋友就不知道如何去回答,甚至一问三不知,从而离我们心仪的off...

    用户8670130

扫码关注云+社区

领取腾讯云代金券