彻底搞懂channel原理(三)

简介: 彻底搞懂channel原理(三)

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

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


1668512570385.jpg


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


1668512580497.jpg


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


1668512591656.jpg


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

具体哪些操作

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

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

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

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

编译这段代码

1668512626803.jpg

1668512635807.jpg

1668512651864.jpg

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

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

1668512755804.jpg

1668512765726.jpg

1668512772678.jpg

1668512785807.jpg

到这里我们已经知道

  • 发送数据,最终调用的runtime.chansend
  • 接收数据,最终调用的runtime.chanrecv

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


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

来看一段简单的程序。


1668512809013.jpg


值得一提的是,在使用go func的时候,本质上调用的是runtime.newproc创建一个g然后把这个g交给调度器调度。

至于什么时候g被调度,然后执行你的代码逻辑,那就要看调度器的"心情"了。

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

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


1668512830246.jpg


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


1668512842651.jpg


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


1668512856836.jpg


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

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


我们来创建一个例子


1668512868189.jpg


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

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

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


1668512880184.jpg


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


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


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


1668512892933.jpg

1668512899553.jpg


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


那么对于接收操作呢?

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


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


1668512913615.jpg


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


第三种情况有点复杂。


1668512943649.jpg


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


1668512952911.jpg


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

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

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


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

1668512968498.jpg


相关文章
|
7月前
muduo源码剖析之channel通道类
channel是muduo中的事件分发器,它只属于一个EventLoop,Channel类中保存着IO事件的类型以及对应的回调函数,每个channel只负责一个文件描述符,但它并不拥有这个文件描述符。channel是在epoll和TcpConnection之间起沟通作用,故也叫做通道,其它类通过调用channel的setCallbcak来和建立channel沟通关系。
111 0
|
2月前
|
Java Linux 应用服务中间件
【编程进阶知识】高并发场景下Bio与Nio的比较及原理示意图
本文介绍了在Linux系统上使用Tomcat部署Java应用程序时,BIO(阻塞I/O)和NIO(非阻塞I/O)在网络编程中的实现和性能差异。BIO采用传统的线程模型,每个连接请求都会创建一个新线程进行处理,导致在高并发场景下存在严重的性能瓶颈,如阻塞等待和线程创建开销大等问题。而NIO则通过事件驱动机制,利用事件注册、事件轮询器和事件通知,实现了更高效的连接管理和数据传输,避免了阻塞和多级数据复制,显著提升了系统的并发处理能力。
66 0
|
7月前
|
存储 消息中间件 安全
27道 Handler 经典面试题,你能答出多少?
27道 Handler 经典面试题,你能答出多少?
|
7月前
|
设计模式 缓存 安全
一篇文章带你吃透Go语言的Atomic和Channel--实战方法
一篇文章带你吃透Go语言的Atomic和Channel--实战方法
113 0
|
7月前
|
Go
Go语言Channel进阶:巧妙运用超时机制
Go语言Channel进阶:巧妙运用超时机制
373 0
|
缓存 移动开发 网络协议
TCP编写服务器,客户端以及遇到的两个问题,Socket,ServerScket 类,flush(),方法。以及多线程解决,及改进的线程池写法,IO多路复用的思想,C10K,C10M的阐述。万字超细
TCP编写服务器,客户端以及遇到的两个问题,Socket,ServerScket 类,flush(),方法。以及多线程解决,及改进的线程池写法,IO多路复用的思想,C10K,C10M的阐述。万字超细
|
开发框架 Java 中间件
到底什么是Java AIO?为什么Netty会移除AOI?一文搞懂AIO的本质!
Java AIO的这些不合常理的现象难免会令人心存疑惑。所以决定写这篇文章时,我不想只是简单的把AIO的概念再复述一遍,而是要透过现象,深入分析、思考和并理解Java AIO的本质。
1093 1
|
Java Linux Windows
通俗易懂的JAVA BIO NIO AIO 原理白话文解释,区别,优缺点及代码使用案例
通俗易懂的JAVA BIO NIO AIO 原理白话文解释,区别,优缺点及代码使用案例
通俗易懂的JAVA BIO NIO AIO 原理白话文解释,区别,优缺点及代码使用案例
|
存储 安全
彻底搞懂channel原理(二)
彻底搞懂channel原理(二)
257 0
彻底搞懂channel原理(二)
|
存储 数据可视化 安全
彻底搞懂channel原理(一)
彻底搞懂channel原理(一)
196 0
彻底搞懂channel原理(一)