Golang和Erlang的IO调度浅析

之前关于调度器的对比分析的文章,在结束时遗留了一些问题:当系统出现高并发的IO访问时,如一个网络服务器通常要并发处理成百上千的链接,每个链接可能都是由一个用户任务执行的,那么将会出现大量阻塞的IO操作,如果为每个阻塞操作都单独分配一个OS线程,那么系统很容易就会退化成多OS线程的系统,轻量任务的优势将无从谈起。本文试图回答这个问题,通过分析Go和Erlang对于IO、特别是网络IO的优化机制,了解其对调度器乃至整个系统性能的影响。

Go的IO优化机制 —— netpoller

由于Go是一门主要面向互联网环境的分布式语言,相对于一般的IO,如文件读写等,网络IO的并发性能更加重要。对于一般IO,Go的处理方式就是按上篇所说的,将执行Syscall的OS线程剥离。通常应用场景下,不会出现大量并发Goroutine去同时读写文件的情况,因而上面的方式并不会真正造成调度器的退化。因此主要的IO优化都是针对io/net库的。

无独有偶,Erlang在实现上同样对网络IO提供了不同于一般IO的高效处理方式,后面再作介绍。

Go实现中利用了OS提供的非阻塞IO访问模式,并配合epll/kqueue等IO事件监控机制;但是为了弥合OS的异步机制与Go接口的差异,Go在其库中做了一些封装,并在runtime层提供了一种叫做netpoller,“网络轮询器”的机制,来实现网络IO优化。具体来说:

  • 首先,无论何时,当在Go中打开或接收到一个链接时,其文件句柄都会被设为NONBLOCKING模式。(Go语言库
  • 当调用相应的Read/Write等操作时,无论是否成功,都会直接返回而不会阻塞。当返回值是EAGAIN时,表示IO事件还没有到达,需要等待。这时,Go库函数调用PollServerAddFd()将对应文件句柄加入netpoller的监控池,并将当前Goroutine阻塞。(Go语言库、netpoll.goc中
  • 当系统中存在空闲 P & M (参见这里) 时,runtime 会首先查找本地就绪队列,若其空,则调用netpoller; netpoller通过OS提供的epoll或kqueue机制,检查已到达的IO事件,并唤醒对应的Goroutine返回给runtime,将其再度执行。(runtime/proc.c:findrunnable()
  • 最后,Goroutine再次回到Go语言库上下文时,再调用Read/Write等IO操作时,就可以顺利返回了。(Go语言库)

Erlang的IO优化机制之一 —— “Async Threads Pool”

在Erlang中,所有IO操作都需要以Port驱动的形式提供,所谓Port驱动包含一组C回调函数,用来响应用户进程的访问;用户进程则通过通用的消息传递机制与Port交互。Erlang虚拟机会把Port当做一种特殊的任务加以调度。

真正的系统调用,如read/write/flush等阻塞式操作都被封装在Port的回调函数之中,当调度器调度执行响应的Port时,就会导致当前的调度器执行线程被OS阻塞,从而影响系统的并行性。

Erlang解决该问题的办法是提供了一组OS线程作为异步线程池,阻塞的IO操作(以函数指针形式)会被Port注册到异步线程池的操作队列中。异步线程则执行循环操作,取出当前任务队列的IO任务并执行阻塞操作。

这种方式类似Go对非net类IO及执行阻塞式Syscall的调度方式:用一个单独的OS线程去执行阻塞操作。

Erlang的file IO基本上就是以上述方式实现的。

因为 Erlang 将调度器映射到一个OS线程而说其调度是1:1的其实是不准确的。基于对阻塞IO的异步处理及上篇讲到的负载平衡机制,使得Erlang实际上也实现了M:N的调度,只不过Erlang的官方文档并没有这么说,只是说单纯增加调度器数不会对性能造成影响。

Erlang的IO优化机制之二 —— “System Level Activities”

如前所述,无论时Erlang还是Go,都是针对服务器端设计的语言,因此都提供了不同于一般IO的特殊机制来处理网络IO。

Erlang的做法是提供一种特别的调度单元 ——System Level Activities,来调度异步IO事件。它的思想和Go的netpoller非常类似:

  • 首先,网络链接对应的句柄会被设为NONBLOCKING状态;
  • 一次IO操作如果在响应事件到来前被调用,则会将其等待的事件注册到Erlang虚拟机的IO事件链中;
  • 调度器在调度时,会周期性的调用check_io操作来检查已注册的IO事件是否已经到来(利用OS的poll操作),并唤醒响应事件阻塞的用户任务(进程或Port)。

值得注意的是,Erlang虚拟机在处理IO事件时,还采用了一种stealing的机制。具体来说,当一个driver的函数调用IO操作时,如果对应IO事件没有到来时,还会主动调用select_steal()窃取其他已注册的IO事件,如果该事件已触发,则完成相应的读/写操作,并通知上层进行后续处理。

Libtask中的异步IO机制

作为Go语言的前身,Libtask库同样实现了异步IO机制,并且实现方式更加简洁。

与Go类似,在Libtask中,为用户级task封装了IO操作,提供了fdread/fdwrite/fdwait/fdnoblock等接口实现异步IO。(在libtask提供的例子中,所有IO操作都是针对网络IO的,因此仅就网络IO情况加以分析。

  • 链接句柄首先会通过调用fdblock()被设为NONBLOCKING态;
  • 之后调用fdread/fdwrite时,一旦返回EAGAIN,则调用fdwait,注册等待IO事件并将自身调出;
  • Libtask会在第一次接收到IO事件注册后建立一个系统任务fdtask,该任务通过调用poll系统调用检查新到来的IO事件,并将对应任务重新加到就绪队列中。

总结及参考

通过上文分析,了解到IO优化对调度器乃至语言本身性能的影响。这与两种语言的应用背景——服务器端编程有很大关系。

通常来说,应用程序必须通过Syscall 访问操作的特定功能,这就会涉及底层 OS 的调度机制,作为用户态的任务调度器,Erlang虚拟机或Go的运行时系统都必须对内核调度引入的不确定性加以控制。特别是 IO 操作这类特殊并且会大量访问到的Syscall,必须设计有针对性的优化方案,才能确保高的并发性能。

Go 和 Erlang 的实现方案随不尽相同,但核心的思想都是类似的,通过异步IO 优化基于Socket的操作,而对于一般的文件读写,则直接让执行线程及运行的用户任务阻塞,调度器再将其他可以执行的任务绑定到其他OS线程继续执行。

这篇文章除了参考了Erlang/OTP及Go语言的源代码外,还参考了以下资料:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


类型转换 1、int转string 2、string转int 3、string转float 4、用户结构类型转换
package main import s "strings" import "fmt" var p = fmt.Println func main() { p("Contains: ", s.Contains("test&quo
类使用:实现一个people中有一个sayhi的方法调用功能,代码如下: 接口使用:实现上面功能,代码如下:
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件信息 4、删除文件,成功返回true,失败返回false
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http://www.golangtc.com/downloadBeego开发文档地址:http://beego.me/docs/intro/ 安装步骤: 一、GO环境安装 二、配置系统变量 三、Beego安装 一、GO环境安装 根
golang获取程序运行路径:
Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考