#0 runtime.gopark (unlockf={void (runtime.g *, void *, bool *)} 0x0, lock=0x7fffd00a0700, reason=2 '\002', traceEv=27 '\033', traceskip=5) at /home/dou/work/go/src/runtime/proc.go:349 #1 0x000000000042e5fe in runtime.netpollblock (pd=<optimized out>, mode=<optimized out>, waitio=<optimized out>) at /home/dou/work/go/src/runtime/netpoll.go:445 #2 0x000000000045c489 in internal/poll.runtime_pollWait (pd=<optimized out>, mode=140736683706112) at /home/dou/work/go/src/runtime/netpoll.go:229 #3 0x000000000048b0b2 in internal/poll.(*pollDesc).wait (pd=<optimized out>, mode=140736683706112, isFile=2) at /home/dou/work/go/src/internal/poll/fd_poll_runtime.go:84 #4 0x000000000048ba1a in internal/poll.(*pollDesc).waitRead (isFile=2, pd=<optimized out>) at /home/dou/work/go/src/internal/poll/fd_poll_runtime.go:89 #5 internal/poll.(*FD).Read (fd=0xc0001a8000, p=..., ~r1=<optimized out>, ~r2=...) at /home/dou/work/go/src/internal/poll/fd_unix.go:167 #6 0x00000000004ac629 in net.(*netFD).Read (fd=0xc0001a8000, p=...) at /home/dou/work/go/src/net/fd_posix.go:56 #7 0x00000000004b6965 in net.(*conn).Read (c=0xc000094008, b=...) at /home/dou/work/go/src/net/net.go:183 #8 0x00000000004c1d2e in net.(*TCPConn).Read (b=...) at <autogenerated>:1
补充一点:gopark 的核心逻辑是,切换到 g0 栈,执行 park_m。在 park_m (g0 栈)中再把当前 G 挂起。
恢复执行
上一步的 park_m 函数,除了会挂起当前 G,另外一个重要的任务就是执行 schedule 函数,挑一个新的 G 开始运行。
在上一篇介绍过,挑选一个新的 G 的过程中,就有 检查 netpoll 这一步。 我们可以在 gdb 中看到如下的调用栈:
1 2 3 4 5
#0 runtime.netpollready (toRun=0x7fffcaffc6c8, pd=0x7fffd00606d8, mode=119) at /home/dou/work/go/src/runtime/netpoll.go:372 #1 0x000000000042f057 in runtime.netpoll (delay=<optimized out>) at /home/dou/work/go/src/runtime/netpoll_epoll.go:176 #2 0x000000000043abd3 in runtime.findrunnable () at /home/dou/work/go/src/runtime/proc.go:2947 #3 0x000000000043bdd9 in runtime.schedule () at /home/dou/work/go/src/runtime/proc.go:3367 #4 0x000000000043c32d in runtime.park_m (gp=0xc000202000) at /home/dou/work/go/src/runtime/proc.go:3516
netpoll 中的核心逻辑即是调用 epoll_wait,获取一批已经准备就绪的 events,恢复这批 G 到 runable 状态,并运行第一个 G。
总结
对于非阻塞网络的实现,核心点是 EAGAIN 和 epoll_wait。 go 语言把这个细节隐藏到了语言/标准库内部,确实很大程度的降低了程序员的心智负担。