Go netpoller

本文翻译自The Go netpoller

摘要

这边文章将介绍 Go 如何处理网络 I/O

阻塞I/O

在Go中,所有的I/O都是阻塞的。 Go生态系统是围绕这样的想法构建的,即你根据阻塞接口进行编写,然后通过goroutines和通道处理并发,而不是callbacks或者futures。一个例子是“net / http”包中的HTTP服务器。只要它接受连接,它就会创建一个新的goroutine来处理在该连接上发生的所有请求。这个架构意味着请求处理程序可以以非常直接的方式写入。首先做这个,然后做那个。不幸的是,使用操作系统提供的阻塞I/O不适合构建我们自己的阻塞I/O接口。

在我之前关于Go运行时的文章中,我介绍了Go调度器如何处理系统调用。要处理阻塞式的系统调用,我们需要一个可以在操作系统内部被阻塞的线程。如果我们要在操作系统的阻塞I/O的基础上构建我们自己的阻塞I/O,我们会为每个停留在系统调用的客户端孵化一个新的线程。一旦你有10,000个客户端线程,这将变得非常昂贵,全部停留在系统调用中,等待他们的I/O操作成功。

Go通过使用操作系统提供的异步接口来解决这个问题,但是阻塞正在执行I/O的goroutine。

netpoller

将异步I/O转换为阻塞I/O的部分称为netpoller。它坐在自己的线程中,从希望进行网络I/O的goroutine接收事件。 netpoller使用操作系统提供的接口来轮询网络套接字。在Linux上,它使用epoll,在BSD和Darwin上使用kqueue,在Windows上使用IoCompletionPort。这些接口的共同之处在于它们为用户空间提供了一种有效查询网络I/O状态的方法。

无论何时在Go中打开或接受连接,产生的文件描述符都被设置为非阻塞模式。这意味着如果你试图对其进行I/O操作,并且文件描述符还没有准备好,它将返回一个错误代码。每当goroutine尝试读取或写入连接时,网络代码都会执行该操作,直到收到这样的错误,然后调用netpoller,告诉它在准备好再次执行I/O时通知goroutine。然后goroutine被调度器从正在运行的线程换出,并重新安排另一个goroutine在它的位置开始运行。

当netpoller收到操作系统通知它可以在一个文件描述符上执行I/O时,它会查看它的内部数据结构,看看是否有任何在该文件上被阻塞的goroutine,并通知它们。然后goroutine可以开始继续执行并成功完成I/O操作。

如果这听起来很像旧的Unix系统中使用 select 和 poll 调用来处理I/O,那是因为它就是如此。不同的是,netpoller并不是查找一个函数指针和一个包含一堆状态变量的结构体,而是查找一个可以调度的goroutine。这样可以让您免于管理所有的状态,也不需要重新检查是否收到了足够的数据。

    原文作者:绝望的祖父
    原文地址: https://www.jianshu.com/p/91bd702d7593
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞