说明
前面的章节我们基本聊完了golang网络编程的关键API流程,但遗留了一个关键内容:当系统调用返回EAGAIN时,会调用WaitRead/WaitWrite来阻塞当前协程,现在我们接着聊。
WaitRead/WaitWrite
1 | 复制代码func (pd *pollDesc) Wait(mode int) error { |
最终runtime_pollWait走到下面去了:
1 | 复制代码TEXT net·runtime_pollWait(SB),NOSPLIT,$0-0 |
我们仔细考虑应该明白:netpollWait的主要作用是:等待关心的socket是否有事件(其实后面我们知道只是等待一个标记位是否发生改变),如果没有事件,那么就将当前的协程挂起,直到有通知事件发生,我们接下来看看到底如何实现:
1 | 复制代码func netpollWait(pd *pollDesc, mode int) int { |
从上面的分析我们看到,如果无法读写,golang会将当前协程挂起,在协程被唤醒的时候,该标记位应该会被置位。 我们接下来看看这些挂起的协程何时会被唤醒。
事件通知
golang运行库在系统运行过程中存在socket事件检查点,目前,该检查点主要位于以下几个地方:
runtime·startTheWorldWithSema(void):在完成gc后;
findrunnable():这个暂时不知道何时会触发?
sysmon:golang中的监控协程,会周期性检查就绪socket
TODO: 为什么是在这些地方检查socket就绪事件呢?
接下来我们看看如何检查socket就绪事件,在socket就绪后又是如何唤醒被挂起的协程?主要调用函数runtime-netpoll()
我们只关注epoll的实现,对于epoll,上面的方法具体实现是netpoll_epoll.go中的netpoll
1 | 复制代码func netpoll(block bool) (gp *g) { |
这个函数主要调用epoll_wait(当然,golang封装了系统调用)来获取就绪socket fd,对每个就绪的fd,调用netpollready()作进一步处理。这个函数的最终返回值就是一个已经就绪的协程(g)链表。
netpollready主要是将该socket fd标记为IOReady,并唤醒等待在该fd上的协程g,将其添加到传入的g链表中。
1 | 复制代码// make pd ready, newly runnable goroutines (if any) are returned in rg/wg |
疑问:一个fd会被多个协程同时进行IO么?比如一个协程读,另外一个协程写?或者多个协程同时读?此时返回的是哪个协程就绪呢?
一个socket fd可支持并发读写,因为对于tcp协议来说,是全双工。读写操作的是不同缓冲区,但是不支持并发读和并发写,因为这样会错乱的。所以上面的netFD.RWLock()就是干这个作用的。
本文转载自: 掘金