当前位置: 首页 > 工具软件 > NeverBlock > 使用案例 >

运用Ruby纤程进行异步I/O:NeverBlock和Revactor

颛孙信厚
2023-12-01

纤程(Fibers)慢慢地进入了Ruby开发者们的视线中,将作为新的并发原语而广为使用。通过对纤程和非阻塞(或者说异步)I/O这两种方法的组合,可以解决用户空间线程问题或者Ruby 1.9的巨型解释器锁(Giant Interpreter Lock,简称GIL)的问题,这个问题使得Ruby语言线程每次只能有一个是激活的。

\

最大的问题要数和使用那些阻塞的系统调用了,尤其是I/O方面的。阻塞的系统调用,就拿读取来说,直到数据准备好的时候才会返回。在用户态的线程系统中,这意味着这个进程中的所有线程也都会阻塞。解决方法是将 I/O系统与阻塞I/O的方式进行解耦。有一种方法是在引发一个阻塞的系统调用以前捕捉它,以一种非阻塞的方式来处理这个I/O请求,然后挂起这个纤程,给另外的纤程运行的机会。一旦系统收到这个I/O请求的回应的话,纤程会再度被调度。

\

纤程提供了一个好用的并发工具来实现这个概念,而不会让使用者去忍受些什么不愉快。目前,两个程序库都着重于使用纤程实现解决方案。比较新的解决方案是NeverBlock程序库(Github代码库包括NeverBlockNeverBlock PG支持ActiveRecord的NeverBlock PG适配器,以及支持异步操作的MySQLPlus MySQL适配器)。更加通用的解决方案是建立在诸如Actor和进程通信等Erlang的思想之上的,来自Tone Arcieri的Revactor程序库。我们对来自NeverBlock项目的Mohammad A. Ali来自Revactor项目的Tone Arcieri进行了访谈。

\

NeverBlock

\

InfoQ: 准确点儿说,NeverBlock做了些什么?依我看,它提供了一个连接池把传入代码包装在纤程当中,等到连接可用的时候再恢复纤程。NeverBlock的主要贡献是什么?

\

Mohammad A. Ali实现池机制对于NeverBlock其他部分的正常工作是必须的。最初的想法是,我们需要为应用程序提供即时的IO并发,例如Rails、Merb或者Ramaze等不支持完全线程安全的应用。

\

想要达到这个目标的话,我们需要一个web服务器来包装来自纤程的请求和IO程序库,纤程来自于NeverBlock的纤程池,而IO程序库(不仅仅是DB而是全部的IO操作)则是那些与纤程有关并利用其进行暂停和恢复请求的程序库。所有的一切都要使用一个诸如EventMachine或者Rev这样的事件循环来进行搭配。

\

InfoQ: NeverBlock::PG 是基于NeverBlock构建的,而且PostgreSQL驱动已经支持了非阻塞I/O且立等可用。如果这样的话,再引入NeverblockPG做什么呢?如果想添加另外一个事件驱动的话,比如说Mysql,还没有非阻塞I/O的支持,那么NeverBlock会提供什么帮助呢?

\

Mohammad A. Ali NeverBlock::PG驱动可以让非阻塞操作透明化,你只需要使从纤程池中spawn一个纤程出来即可。这样的话,你就可以这么写程序:

\
pool.spawn do
 res1 = db.exec(query1)
 res2 = db.exec(query2_that_depends_on_query1)
end
\

一个普通的非阻塞实现会比这更加复杂。多亏了纤程,我们可以写出这种看似明明是阻塞的代码,却能够以非阻塞的行为运行。

\

对于NeverBlock::PG驱动,我们再更深入一点。我们刚刚发布了一个全新的activerecord-neverblock-postgresql适配器,将非阻塞IO带到了 ActiveRecord中。我觉得很容易猜到NeverBlock的下一个目标是什么了。

\

最有意思的是,当你将NeverBlock应用于全栈应用的时候,它能以一种几乎透明的方式来实现。

\

NeverBlock不能让一个阻塞的驱动变得非阻塞。它能让一个非阻塞的驱动的操作看起来更像是阻塞的方式,而不会牺牲非阻塞的特性。这既是说,我们未来的努力方向是类似于Asymy的(事件化mysql驱动)。

\

InfoQ: 你的程序库需要纤程(Fibers)的支持,而纤程只有Ruby 1.9才支持。你认为这对用户来说是一个问题吗(考虑到Ruby 1.9依然变化频繁而且并没有太多人去尝试)?你的程序库的特性会不会对用户去尝试Ruby 1.9起到一个刺激作作用呢?

\

Mohammad A. Ali 我相信纤程是Ruby 1.9中最棒的事情之一。同样的功能可以通过Ruby 1.8的continuation实现,但是性能上要差得多。另外稳定的1.9目前离发布也为期不远了,我觉得我们真的应该前行了。我相信 NeverBlock、Revactor或者类似的东西提供的优点会帮助大家心甘情愿地转换过去的。

\

InfoQ: 看了看NeverBlock开放的源代码:你对Fiber使用了开放类并加入了一些方法。这么做的目的是什么?

\

Mohammad A. Ali 纤程对于存储纤程本地变量方面的功能是缺失的,然而线程可以做到。我需要这些功能来替代事务的当前ActiveRecord实现并使得纤程可查。我还需要它们来做到非阻塞操作的可选性,还有线程的上下文等等。

\

InfoQ: 你听说过Revactor吗?

\

Mohammad A. Ali 听过,我还搞过一点Revactor。实际上我用Rev做为NeverBlock除了EventMachine以外的第二后端(仅仅是实验性质的)。但是 NeverBlock和Revactor的目的不同。Revactor为Ruby引入了一个新的并发模型,而NeverBlock目的是以最小的变化把并发引入当前的Ruby程序中。

\

编辑注:在本次采访完成之时,第一个异步MySQL驱动的尝试──MySQLPlus已经可用了。

\

Revactor

\

InfoQ: Revactor的当前状态是什么?

\

Tony Arcieri: 在无数的项目当中有些被忽视,然而在很多商业应用上已经获得了成功。

\

InfoQ: Revactor新版本的计划是什么?

\

Tony Arcieri: 最近Aman Gupta发布了一个“poor man's Fibers”的Ruby 1.8的实现。这样的话将Revactor移植到1.8也是可以的了,然而性能上可能会很差。

\

目前,Revactor还只是支持将全部的Actors放在同一个Ruby线程中。而更好的做法是可以在不同的线程中运行的Actors之间互相传递消息,但是目前还做不到。我已经和一些有兴趣实现它的人们聊过,希望很快就可以将这种方式引入Revactor。

\

InfoQ: 用户需要直接对Revactor的actors进行编程吗?还是可以使用RevActor为其他的程序库实现后端,这样可以(对开发者)透明化?

\

Tony Arcieri: Revactor 和Rubinius中的Actor实现基本上兼容,但是目前Rubinius中的Actors还没有一个简单的方法可以像Erlang的gen_tcp那样进行网络编程。这也就是说,开发者们想使用Actors编写网络应用的话,可以先从Revactor开始,将来再移植到Rubinius上。

\

InfoQ: 你是如何调度阻塞I/O请求的?你使用内核线程来运行I/O请求吗?

\

Tony Arcieri: 当所有已发现的Actor消息都处理了、而且没有其他Actors是运行态的时候,Revactor使用一个我编写的、叫做Rev(和 EventMachine类似)的事件程序库来监控I/O事件。Rev使用Ruby 1.9中的rb_thread_blocking_region()函数来做到阻塞监控I/O读取的系统调用,因此不需要再使用独立的内核线程。 Revactor动态地扩展了Ruby的TCPSocket类(Revactor::TCP),这样能做到看起来像阻塞的调用方式,但是实际上则是传回给 Actor调度器。对现存的库进行Moneky Patch(动态打补丁)来做到Revactor::TCP替换Ruby Sockets是很容易做到的。比如说,Revactor就发布了一个小补丁,来实现在Mongrel中使用Actors而不是线程来并发。

\

InfoQ: 你如何处理I/O请求序列?是批量请求处理吗?

\

Tony Arcieri: 从表面上看,调用方式是“阻塞”的。当一个I/O请求处理结束后,当前的Actor就会休眠,并在下一次I/O竞争发生的时候被重新调度。这使得那些依赖于阻塞方式接口的程序库可以很方便的构建在Revactor之上。比如说ActiveRecord或者DataMapper之类的(目前还没有运行在 Revactor之上)。

\

InfoQ: Rubinius上Actors的状态是什么?

\

Tony Arcieri: 它做了所有Revactor能做的事,并且宣布将支持TCP套接字“active模式”。这意味着在从TCP套接字中读取输入的时候,和以前不同的是,传入的数据是通过标准内部Actor消息异步送达到指定的Actor的。这使得Actor可以并行处理I/O和内部Actor消息。Rubinius虚拟机目前正在使用C++重写,在重写之后希望可以包含实现“active模式”消息传递的全部特性。当它一旦可用的时候,我就会去尝试一下。

\

InfoQ: 有计划让Revactor支持Rubinius吗?

\

Tony Arcieri: 没有,Revactor大量的利用到了YARV的特性。Rubinius有一个很棒的并发模型和以Task和Channel的形式的I/O支持,而且Rubinius现有的Actor实现让这两者都非常的有效率。Revactor和Rubinius Actors在duck type方面很大程度上是相似的,所以编写两者都兼容的程序并不是一件很头疼的事情。

\

InfoQ: 在Rubinius上运行Actors或者Revactor有什么优势呢(超过Ruby 1.9)?

\

Tony Arcieri: 现在的话还是Ruby 1.9平台更好一些,对于现有的库有着更好的兼容性。Rubinius正在开发当中,而且目前在重写。在将来,Rubinius上会有很多优势能超越 Ruby 1.9,比如用于并发和I/O的Task/Channel抽象就会比Ruby 1.9中现存的更加清晰。在1.8和1.9上用于I/O的解决方案一直都是运行一个诸如EventMachine或者Rev的事件框架,与Ruby内建的 I/O并行工作,而Rubinius在I/O方面一开始就已经占优势了。

\

InfoQ: 你知道使用Revactor的项目吗?

\

Tony Arcieri: 我已经听说了很多人在一些内部项目中使用它了,主要是用于并发HTTP客户端。我还不知道任何已经发布的项目用到了它。

\

InfoQ: 依赖Ruby 1.9的话,会不会对Revactor或者使用纤程的库的推广造成阻碍?

\

Tony Arcieri: 我当然可以想象的到这种情况。我也被Ruby 1.9的bug折磨过,所以能想象绝大部分的试用者都会小心翼翼的使用的。我已经了解到最近很多利用纤程来实现事件框架的项目冒了出来,比如Ry Dahl的Flow web server。

\

查看英文原文:Using Ruby Fibers for Async I/O: NeverBlock and Revactor

\

志愿参与InfoQ中文站内容建设,请邮件至editors@cn.infoq.com。也欢迎大家到InfoQ中文站用户讨论组参与我们的线上讨论。

 类似资料: