Golang和Erlang的IO调度浅析
Go的IO优化机制 —— netpoller由于Go是一门主要面向互联网环境的分布式语言,相对于一般的IO,如文件读写等,网络IO的并发性能更加重要。对于一般IO,Go的处理方式就是按上篇所说的,将执行Syscall的OS线程剥离。通常应用场景下,不会出现大量并发Goroutine去同时读写文件的情况,因而上面的方式并不会真正造成调度器的退化。因此主要的IO优化都是针对io/net库的。 无独有偶,Erlang在实现上同样对网络IO提供了不同于一般IO的高效处理方式,后面再作介绍。 Go实现中利用了OS提供的非阻塞IO访问模式,并配合
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非常类似:
值得注意的是,Erlang虚拟机在处理IO事件时,还采用了一种stealing的机制。具体来说,当一个driver的函数调用IO操作时,如果对应IO事件没有到来时,还会主动调用 Libtask中的异步IO机制作为Go语言的前身,Libtask库同样实现了异步IO机制,并且实现方式更加简洁。 与Go类似,在Libtask中,为用户级task封装了IO操作,提供了fdread/fdwrite/fdwait/fdnoblock等接口实现异步IO。(在libtask提供的例子中,所有IO操作都是针对网络IO的,因此仅就网络IO情况加以分析。)
总结及参考通过上文分析,了解到IO优化对调度器乃至语言本身性能的影响。这与两种语言的应用背景——服务器端编程有很大关系。 通常来说,应用程序必须通过Syscall 访问操作的特定功能,这就会涉及底层 OS 的调度机制,作为用户态的任务调度器,Erlang虚拟机或Go的运行时系统都必须对内核调度引入的不确定性加以控制。特别是 IO 操作这类特殊并且会大量访问到的Syscall,必须设计有针对性的优化方案,才能确保高的并发性能。 Go 和 Erlang 的实现方案随不尽相同,但核心的思想都是类似的,通过异步IO 优化基于Socket的操作,而对于一般的文件读写,则直接让执行线程及运行的用户任务阻塞,调度器再将其他可以执行的任务绑定到其他OS线程继续执行。 这篇文章除了参考了Erlang/OTP及Go语言的源代码外,还参考了以下资料:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |