专访开源项目Amoeba架构师陈思儒
DBA notes站长冯大辉(Fenng)代表InfoQ中文站采访了分布式数据库Proxy开源项目Amoeba的架构师和主要开发者陈思儒,内容包括Amoeba项目的起因、功能及其愿景等。
作者 Werner Schuster译者 Jason Lai 发布于 2007年5月27日 下午7时0分
最近的一次对Ruby创始人Matz(Yukihiro Matsumoto,松本行弘)和YARV创始人笹田耕一(Sasada Koichi)的采访,就Ruby对线程的处理这个话题进行了深入探讨。目前,Ruby的稳定版本使用的是用户空间线程(user space threads,也称为“绿色线程 [green threads]”),也就是说Ruby解释器负责线程调度的每一个细节。这就和内核线程(Kernel Threads)形成了对比,在后者中,线程的创建、调度和同步都是由OS的系统调用完成的,这使得这些操作代价高昂——至少和它们在用户空间线程中的等价物相比确实如此。从另一个角度来说,用户空间线程无法利用多核或者多CPU(因为OS不知道这些线程的存在,因此无法在这些核/CPU上调度它们)。
Ruby 1.9在近期将YARV作为新的Ruby VM整合了进来,这是1.9中带来的改变之一,它将内核线程引入了Ruby。内核线程(也叫“原生线程[native threads]”)的引入赢得了广泛掌声,尤其是来自Java和.NET平台的开发人员更是交口称赞,因为这两个平台下用的就是内核线程。尽管如此,阻碍还是存在着。笹田耕一解释道:
大家知道,YARV支持原生线程。这就是说,你可以并发地把每一个Ruby线程运行在每一个原生线程上。
这并不是说,每一个Ruby线程都是并行运行的。YARV里有一把全局VM锁(全局解释器锁),只有唯一在运行的Ruby线程才能拥有。我们大家可能很乐于看见这样的决定,因为我们可以把用C语言写成的大部分扩展运行起来,而不需要进行任何改动。
这就意味着:不管存在着多少个内核或者CPU,只有一个Ruby线程能在任意给定时间里运行。解决方法还是存在的,原生的扩展可以以更加灵活的方式处理全局解释器锁(Global Interpreter Lock,GIL),例如,在开始长时间操作之前将锁释放。笹田耕一解释了释放GIL的可用API:
你必须在进行阻塞操作之前释放巨型VM锁。如果你需要在扩展库中这么做,请使用rb_thread_blocking_region()这个API。
API:rb_thread_blocking_region (
blocking_func, /* function that that will block */
data, /* this will be passed above function */
unblock_func /* if another thread cause exception with Thread#raise,
this function is called to unblock or NULL */
)
问题在于:这样做有效地排除了对内核线程最大的赞成观点——对多核或者多CPU的利用,而又保留了内核线程的问题。
内核线程的引入也是Continuation可能在今后的Ruby版本中移除的原因。Continuation是协作调度(cooperative scheduling)的一种方式,即吧一个线程中执行的操作显式地转给另一个来控制。这个特性也以“协同程序(Coroutine)”之名为人所知,并且存在了很长的一段时间。最近,因为基于Smalltalk的Web框架Seaside使用了Continuation非常显著地简化了Web应用,它也逐渐开始在公众眼前亮相。
这个结合GIL使用内核线程的方式和Python的线程系统是很相似的,后者同样使用GIL,而且用了很长一段时间。Python的GIL引发了无数论战,探讨怎样将其移除,尽管争论热火朝天,GIL仍然悍然不动。
然而,我们考察一下Python语言的创立者Guido van Rossum对于线程的看法,不难发现Ruby线程调度可选的一条未来之路。在最近一篇关于GIL的帖子里,Guido van Rossum解释说:
然而,没错,GIL并不像你最初想象的那么坏:你要做的就是赶快从Windows和Java支持者的洗脑中恢复过来,他们似乎认为线程仅仅是同步活动的唯一实现方式。
仅仅因为Java曾经以在不能支持多地址空间的机顶盒OS上运行作为目标,或者只是由于在Windows中创建进程曾经慢得和狗一样,并不意味着与线程相比,多进程(加上对IPC的合理使用)对于多CPU机器来说就不是一种好多得多的方法。
你所要做的,就是对加锁、死锁、锁的粒度、活锁、非确定性和竞争条件(race conditions)的邪恶组合说“不”。
关于共享地址空间、有抢先调度权的线程所能带来的益处的争论由来已久。Unix作为单线程或者用户空间线程系统的时间最长,它的并行操作是以多个线程通过不同的进程间通信(InterProcess Communication,IPC)的形式(如管道、先进先出[FIFOs]或者显式共享的内存区)来实现的。这是通过fork系统调用的方式支持的,这种方式可以以低廉的代价复制正在运行的进程。
近来,诸如Erlang之类的语言因为同样使用了一种无共享(share nothing)的方式(也称为“轻量级进程”)+简单的IPC方法,开始受到青睐。“轻量级进程(lightweight processes)”并不是OS进程,实际上存在于相同的地址空间之内。它们之所以被称为“进程”,是因为它们无法窥探彼此的内存空间。“轻量级”则是由于它们是由用户空间的调度程序处理的。在很长一段时间内,这意味着Erlang拥有和其它在用户空间进行线程调度的系统一样的问题:不支持多核或者多CPU,并且阻塞性系统调用将阻塞所有线程。不过最近,有人采用了m:n的方式解决了这些问题:目前Erlang运行时使用多个内核线程,每个线程都运行着一个用户空间调度器。这就是说,现在Erlang可以利用多核或者CPU,而且不需要改变自身的运行模式。
Ruby社区很有幸,Ruby团队已经知晓此事,并且考虑将它作为Ruby线程调度的未来方向:
[...]如果我们在一个进程内有多个VM实例,这些VM就可以同步运行。我会在近期着手此事(作为我的研究课题)。
[...]如果原生线程存在许多许多问题,我将实现绿色线程。大家知道,相比原生线程它有一些优势(如轻量级线程创建等)。这会是一次很有趣的Hack过程(跟大家说一声,我的毕业论文就是在我们特定的SMT CPU上实现用户级线程库)。
这就表明,Ruby的用户空间(绿色)线程版本并不会从议事桌上撤离,特别是因为在不同OS上线程系统的实现问题,例如这个问题:
使用原生线程编写代码有它自身的问题。例如,在MacOSX上,如果有其它线程运行的话(,exec()不能正常工作(会引发异常)(这是移植性问题之一)。如果我们在使用原生线程时发现严重问题,我会把绿色线程的版本放到代码主干上(YARV)。
为什么会需要笹田耕一的多VM(Mutilple VM)方案呢?运行多个Ruby解释器,并使它们以IPC方式进行通信(例如,通过Socket)在今天看来也是可能的。然而,这样会带来一系列问题:
当然,这些问题导致了这种方法比用Thread来启动一个新的执行线程要复杂得多:
x = Thread.new {
p "hello"
}
或者也比这个Erlang范例要复杂:
pid_x = spawn(Node, Mod, Func, Args)
这段Erlang代码产生了一个新的轻量级进程,而且这确确实实就是所有它需要的代码。所有的配置代码都已经处理好了,问题中没有一个解释了上面的原因。
这个pid是新产生进程的句柄,并且支持如简单通讯这样的操作:
pid_x ! a_message
这段代码会向pid_x变量存放的pid对应的线程发送一条简单消息。消息可以包含不同的类型,例如原子(Atoms)——Erlang下的Ruby符号(Symbol)等价物。
像这样简单的IPC在Ruby中理所当然可以实现。Erlectricity是一个新的支持Erlang和Ruby间通信的库,但它同样可以用在Ruby VM之间。Erlang IPC尤其有意思,因为它使用了一个模式匹配的方式来辅助消息传递,并使自身变得非常简洁。
毫无疑问,Ruby MVM是对Ruby线程的未来最有希望的设想。它避免了GIL和手动管理Ruby进程的问题,并且使用了“无共享”的理念,这个理念使得Erlang还有其它系统在并行计算方面非常引人注目。
JRuby是唯一一个使用内核线程的Ruby实现,主要原因在于它运行在支持内核线程的JVM之上。创建内核线程的开销在一定程度上因为线程池(事先创建出若干空闲线程,并在需要时取用)的使用而抵消掉了。IronRuby对线程进行支持的细节目前尚不清楚,但由于CLR和JVM非常相近,它很可能也会使用内核线程。
为Ruby MVM的想法创建原型并进行实验的可能性之一,就是在同一个JVM内部启动多个JRuby实例,并让它们之间互相通信。这样就能很有效地带来同样的低廉的IPC(只要数据是只读的,它们就可以很容易通过传递指针的方式传递)。
Ola Bini最近撰文阐述了他关于jrubysrv的想法,这个想法允许运行在一个JVM内部运行多个JRuby实例,以节省内存。
看起来未来在Ruby中进行线程支持的细节仍然有待决定,并且可能在不同的实现中各有差别。
查看英文原文:The Futures of Ruby Threading
对 Ruby 标准实现采用用户空间线程的争论,一直是人们在质疑 Ruby 是否能企业化的主要论据之一。
这篇新闻写得很深入,从多个角度分析了 Ruby 目前的线程模型,并借鉴了 Python、Erlang 等多个动态语言的线程系统,结合 Ruby 核心团队的观点,对 Ruby 运行时未来可能支持的线程模型进行了展望。
如果 Ruby 1.9 以后真正能支持原生线程的话,那么说明 Ruby 向企业化又迈进了一大步。
我个人还是很看好使用原生线程+线程池的想法。
不过文章似乎没有讨论到其他的 Ruby 实现,比如 Ribinius 和 XRuby。前者似乎能够支持多种线程模型,包括用户空间线程和轻量级进程模型。后者,由于采用的是将 Ruby 代码编译成 Java 字节码的方式,我想应该只能支持原生线程了吧?是不是可以在今后默认采用线程池的方式,并由 runtime 来进行管理,以减小线程创建的开销呢?
XRuby目前采用的是Native thread模型,主要是因为JVM采用的就是这个模型。由于Native thread模型是在OS上包了一层,因此Java,XRuby的语义具有不确定性,这将引起一系列的问题,比如同步,加锁,锁粒度,死锁等等。如果想更详细的了解这些问题,可以参考比较新的论文《The Problem with Threads》。
多核的出现,使得软件人员必须去解决Concurrent问题。目前,Erlang语言提供了一个很好的方向,它抛弃了大家熟悉的Thread模型。
目前,打算借鉴Scala,在XRuby里采用与Erlang类似的并发范式。
DBA notes站长冯大辉(Fenng)代表InfoQ中文站采访了分布式数据库Proxy开源项目Amoeba的架构师和主要开发者陈思儒,内容包括Amoeba项目的起因、功能及其愿景等。
作为三期系列文章的第二部分,本文延续了上一期内容,介绍了RichFaces,包括如何把RichFaces集成到之前提到的示例应用中、如何部署RichFaces porlet和RichFaces的多种特性和功能。
Amazon Web Services(AWS)的传道者Jeff Barr讨论了SimpleDB、S3、EC2、SQS、云计算、Amazon的不同服务如何与应用交互、AWS的起源、SimpleDB和微软SQL Server Data Services、AWS cloud的全球化、三月份的AWS停机。
Erlang的并发模型很有名,它的健壮性也很有名。但其他方面呢?在这篇文章里,Dennis Byrne演示了如何用Erlang建立内部DSL。
本视频主要以FreeWheel为例,对一个基于Rails的企业级应用进行了剖析。其中包括:FreeWheel的架构、部署、数据库的问题、REST API、敏捷开发过程、如何去写测试以及持续集成等等。
JavaFX显示了Sun的Java系列产品市场方向的一个重大转变。随着1.0版的即将发布,InfoQ以JavaFX预览版为参考,与Sun高级工程师Joshua Marinacci探讨了即将发布的1.0正式版。
本文主要讲述了如何用JBoss Portlet Container 和JBoss Portlet Bridge创建新项目,怎样配置一个JSF应用去使用JBoss Portlet Bridge,以及JBoss Portlet Bridge所具备的功能。
2 条回复
回复