0%

《Golang》Netpoll 解析

看这篇文章前,建议先把 golang 的源码使用可以跳转函数的 IDE(比如 Goland、Vscode)打开

介绍

网络轮询器 netpoll 类似于 nodejs 中的 libuv,都是对多个系统 IO 模型( linux:epoll,freebsd: kqueue,windows: iocp )的封装,屏蔽各个平台的差异

不能说 netpoll 或 libuv 是异步 IO 模型或者多路复用同步 IO 模型,因为他两在不同操作系统下使用的模型不一样,比如 Linux 上使用 epoll,是多路复用同步 IO 模型,而 Windows 上使用 iocp,是异步 IO 模型

所有的文件 I/O、网络 I/O 和计时器都是由网络轮询器管理的,它是 Go 语言运行时重要的组成部分

Netpoll 网络轮训器提供了一些抽象接口,只需要传递 fd 以及一个回调函数,可以让你很方便的监听 fd 的动向,并自动执行你想要的动作

调度器和 sysmon 会轮询操作系统所有的 fd 的动向(事件),并执行对应的函数

原理

什么情况下会进行初始化

  1. 通过 net.netFD.init 和 os.newFile 初始化网络 I/O 和文件 I/O 的轮询信息时
  2. 添加 timer 时

上面两种情况都会进行 netpoll 的初始化(已经初始化就不用再初始化)

timer 为什么也参与

每个 p 结构体中都包含一个 timer 的最小四叉堆

P 每次调度都会执行 checkTimers 函数,函数中都会 rebalance 计时器的最小四叉堆

然后检查并运行所有到达时间的 timer(运行 timer 结构体中的 f 函数)

netpoll 中的 poll_runtime_pollSetDeadline 函数用来设置 pollDesc 结构体的 deadline 相关字段,可以为每个 fd 设置读超时以及写超时

其原理就是为 pollDesc 结构体添加一个 timer, timer的 f 函数设置为 netpolldeadlineimpl 函数

netpolldeadlineimpl 函数会运行 pollDesc 结构体的协程,即使 pollDesc 中的事件没有被 epoll 触发,因为 deadline 到了

初始化步骤(以 linux 的 epoll 举例)

  1. epollcreate1 系统调用创建 epoll 实例
  2. 创建一个管道,生成 r、w 两个文件描述符,r 和 _EPOLLIN 事件传给 epollctl(代表 r 可读时触发)
  3. epollctl 系统调用,将 r 以及 _EPOLLIN 事件打包成 epollevent 事件加入监听

这样的话,如果往 w 中写数据,因为 r 和 w 是一个通道,所以 r 将变得可读,epoll 将会被监测到有事件触发,epollwait 取事件循环就被中断了

所以将 r 以及 _EPOLLIN 事件打包成 epollevent 事件加入监听,提供了中断 epollwait 无限循环(就是下面的 netpoll 函数中的无限循环)的方式

netpollBreak 方法就被用来写 w,中断 epollwait 无限循环,唤醒 epoll,可用于终止 epoll 事件循环

netpollopen 函数(链接到了internal/poll.runtime_pollOpen函数)可以用来添加事件监听,internal/poll.fd.Init 方法就会添加事件监听

netpollclose 函数(链接到了internal/poll.runtime_pollClose函数)用来移除事件监听

epoll 什么时候启动事件循环更新 fd 状态或唤醒 g

网络轮询器并不是由运行时中的某一个线程独立运行的,运行时中的调度器或 sysmon 会通过 runtime.netpoll 方法向操作系统获取事件,然后更新 fd 状态或唤醒 fd 中休眠的 g

  1. 逻辑处理器 proc.go 中的调度 schedule 函数、startTheWorldWithSema 函数中
  2. 逻辑处理器 proc.go 中的系统监控 sysmon 函数中,判断如果已经初始化了且上次查找距离现在超过了 10ms 就调用 netpoll 函数启动事件循环。

netpoll 函数,会阻塞,有事件触发就会找到并返回需要重新运行的所有协程,函数也会退出

事件循环启动过程

  1. epollwait 等待事件触发,这个函数可以阻塞也可以不阻塞,运行时采用了不阻塞的方式,返回值 n 小于 0 代表没有事件被触发,于是会重新执行 epollwait
  2. 如果 n 大于 0,代表有 n 个事件被触发,for 循环这 n 个事件
  3. 如果是 netpollBreak 唤醒事件,则忽略不处理
  4. 如果是其他事件,则从事件对象中取出 pollDesc 结构体,进入 netpollready 函数处理(从 pollDesc 结构体中取出协程,放入需要重新运行的所有协程数组中)
  5. 返回需要重新运行的所有协程

写文件的时序图




微信关注我,及时接收最新技术文章