为了账号安全,请及时绑定邮箱和手机立即绑定

【九月打卡】第22天 go的-go Network Poller工作流程

标签:
Go

课程名称:深入Go底层原理,重写Redis中间件实战

课程章节:8-5,8-6

课程讲师:Moody


课程内容:

https://img1.sycdn.imooc.com/632fb56c000144cf21161397.jpg

※  初始化netpoll 

https://img1.sycdn.imooc.com/632fbcc000016d7c14600693.jpg

  • netpoll是屏蔽了系统差异的,在不同的系统上,netpollinit会调用不同的方法,linux上就是epoll,windows就是IOCP

  • go整个生命周期内只会初始化一次netpoll

※pollCache  & pollDesc

https://img1.sycdn.imooc.com//63302e1a0001549214120204.jpg

pollCache结构体是链表的头,他有一个lock 锁是用来锁整个链表的,防止并发冲突。同时,他的first是一个pollDesc结构体,是链表的第一个元素的指针。

https://img1.sycdn.imooc.com//63302e2a0001c79313900867.jpg

  • pollDesc是链表的元素类型。他实际是是一个go对socket的描述。描述哪些协程对哪些socket感兴趣。

  • fd就是socket的文件标识符。

  • link指向下一个元素。

  • rg,wg: 1,2 或者是等待读或者等待读或者等待写的协程。等待写是因为网络的带宽有限,写的时候难免会有阻塞的问题,发包要一个个来,等前面的包发完了,才能结束等待。

  • 链表示意图如下图

https://img2.sycdn.imooc.com/632fd99500016d8919020246.jpg

※poll_runtime_pollOpen

https://img4.sycdn.imooc.com/632fdd240001874613131371.jpg

  1. 根据传参fd,创建一个pollDesc并加入到链表里

  2. 给这个pollDesc增加数据,初始化pollDesc,wg和rg初始数值都是0

  3. 把这个pollDesc的协程关心的socket事件注册到epoll里面

※network poll的读写

    ※※ socket可以读写

  • runtime循环调用netpoll方法,此时是g0协程在调用此方法

    https://img1.sycdn.imooc.com//63302e3600013c6214031113.jpg

  1. epollwait会调用系统的epoll_wait(实际上已经改进为epoll_pwait,带p的是安全返回,epoll_pwait()允许应用程序安全地等待,直到文件描述符准备就绪或直到信号被捕获。) 去拿事件池里的事件,如果事件数量n大于0,则进行循环处理

  2. 循环每一个事件,如果属于(_EPOLLIN|_EPOLLRDHUP|_EPOLLHUP|_EPOLLERR),则该事件有读事件,如果属于(_EPOLLOUT|_EPOLLHUP|_EPOLLERR)则拥有写事件,两个事件是可以并存的,也就是说当前socket读写都有。

  3. 最后执行netpollready

    ※※ netpollready

    netpollready只是把数据标记为可读/写,并不会操作数据

    https://img3.sycdn.imooc.com/633017f0000125d915051326.jpg

  1. netpollready在读取事件之后,调用netpollunblock进行处理

  2. netpollunblock会根据当前的模式进行处理,他会先读取pd的pollDesc的rg或者wg(按模式来),如果是0,说明是刚创建的,会被后续的CAS操作替换为pdReady=1,表示已经可以读/写了

    ※※ 什么时候协程会去读写数据

    当协程想要读写数据的时候,会调用poll_runtime_pollwait

    https://img1.sycdn.imooc.com/63301d990001eba619421551.jpg

※场景一  当协程想读写socket的时候,socket数据已经准备好,pdReady=1

    poll_runtime_pollwait主要用来检查这个协程关心的socket是否已经是pdReady状态,主要逻辑在netPollblock里面,netPollblock里会进行判断,如果已经是pdReady就会返回true,表示检查成功。接着业务就可以开始读写操作socket了。

※场景二  当协程想读写socket的时候,socket还没有准备好

  • 首先runtime的g0协程循环调用netpoll方法,但是由于socket没有事件产生,也就是说,netpoll不停的调用epollwait 是拿不到我们的协程关心的那个socket的读写事件的。

  • 协程去找socket进行读写的时候,依然会先调用poll_runtime_pollwait.。由于当前的socket事件没有发生,所以就不会改pollDesc里面的rg和wg的状态,初始状态依然为0

  • poll_runtime_pollwait经过判断,认为socket处于繁忙状态。rg或者wg的状态修改为pdWait=2。

  • 当协程去读取的时候,poll_runtime_pollWait调用netpollblock会查看rg或者wg状态,如果为pdWait,则在for循环中break掉

  • 调用gopark让协程休眠,在调用gopark的时候,会传入一个方法 netpollblockcommit,这个方法会把rg或者wg的指针修改为当前协程,一旦eventPoll里面有数据,且不是初始化状态,就会使用run唤醒该协程,继续执行其对socket的读写任务


点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
PHP开发工程师
手记
粉丝
2
获赞与收藏
4

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消