InfoQ

InfoQ

新闻

我的书签

登录注册 以永久保存书签。

该内容已经被标记书签!

标记书签错误,请重试!

Ruby线程模型之未来

作者 Werner Schuster 译者 Jason Lai 发布于 2007年5月27日

领域
架构 & 设计,
语言 & 开发
主题
Ruby ,
编程
标签
Erlang ,
并发 ,
JRuby ,
线程技术

最近的一次对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)在今天看来也是可能的。然而,这样会带来一系列问题:

  • Ruby进程需要运行一个新的Ruby解释器,这就意味着进程必须了解自己是如何启动的(应该使用哪个Ruby可执行程序)。这很快会变得很难通过一种可移植的方式完成。举例而言:如果使用了JRuby,那么可执行程序就该是“JRuby”。更糟糕的情况:运行它的JVM或者应用服务器可能不会允许在程序之外运行;
  • 新的Ruby解释器必须使用正确的环境变量、LOADPATHs、包含文件路径和要运行的主.rb文件设置起来;
  • 通信可以通过DRb来产生,但必须由网络来完成,而这是唯一一种具备可移植性的IPC形式;
  • 网络通信就意味着要用到协商端口(即哪一个端口应成为两个互相监听的程序的“服务端”);
  • 网络通信还意味着与防火墙之间可能存在的问题,如受到程序打开连接或者端口的困扰。

当然,这些问题导致了这种方法比用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

值得推荐的新闻,深入有见地 发表人 Lai Jason 发表于
XRuby的线程模型 发表人 Yu Zhang 发表于
  1. 返回顶部

    值得推荐的新闻,深入有见地

    发表人 Lai Jason

    对 Ruby 标准实现采用用户空间线程的争论,一直是人们在质疑 Ruby 是否能企业化的主要论据之一。

    这篇新闻写得很深入,从多个角度分析了 Ruby 目前的线程模型,并借鉴了 Python、Erlang 等多个动态语言的线程系统,结合 Ruby 核心团队的观点,对 Ruby 运行时未来可能支持的线程模型进行了展望。

    如果 Ruby 1.9 以后真正能支持原生线程的话,那么说明 Ruby 向企业化又迈进了一大步。

    我个人还是很看好使用原生线程+线程池的想法。

    不过文章似乎没有讨论到其他的 Ruby 实现,比如 Ribinius 和 XRuby。前者似乎能够支持多种线程模型,包括用户空间线程和轻量级进程模型。后者,由于采用的是将 Ruby 代码编译成 Java 字节码的方式,我想应该只能支持原生线程了吧?是不是可以在今后默认采用线程池的方式,并由 runtime 来进行管理,以减小线程创建的开销呢?

  2. 返回顶部

    XRuby的线程模型

    发表人 Yu Zhang

    XRuby目前采用的是Native thread模型,主要是因为JVM采用的就是这个模型。由于Native thread模型是在OS上包了一层,因此Java,XRuby的语义具有不确定性,这将引起一系列的问题,比如同步,加锁,锁粒度,死锁等等。如果想更详细的了解这些问题,可以参考比较新的论文《The Problem with Threads》。
    多核的出现,使得软件人员必须去解决Concurrent问题。目前,Erlang语言提供了一个很好的方向,它抛弃了大家熟悉的Thread模型。

    目前,打算借鉴Scala,在XRuby里采用与Erlang类似的并发范式。

深度内容

大规模视频网站的计费与流量管理

本次分享将会就大规模视频网站的计费与流量管理这个话题,从操作层面细细进行讲解和分析,为系统工程师们揭示平日里我们没有关心的另一些内容。同时也希望本次分享能揭示行业中的一些“潜规则”,让互联网行业的流量与带宽管理更为开放与简洁。
本次演讲视频录制于QCon杭州2011

专访Jeffrey Richter:Windows 8是微软的重中之重

Jeffrey Richter以其多本Windows核心技术的经典著作而闻名,同时,他深入掌握微软的.NET等一系列核心技术,2012年1月,Jeffrey Richter在北京接受了InfoQ中文站的专访,谈到Windows 8和WinRT编程,并就异步编程、Windows编程中的可扩展性、性能和安全性方面给出自己的建议。

应用云平台的可用性——从新浪SAE看云平台设计

云计算平台的可用性,相比传统互联网服务而言,更加复杂和困难,也更具有挑战性。本文借助新浪SAE云平台为读者讲述了云平台可用性的定义、如何打造高可用的平台,以及对云计算的用户提出了建议。

JVM定制改进 @ 淘宝

淘宝高度重视Java平台的健康发展,组建了一个团队专注于Java平台的底层部分的性能、功能与稳定性改进;工作主要基于OpenJDK中的HotSpot VM开展,其中一些通用的功能随后也会逐渐反馈给OpenJDK社区。希望能与使用Java平台开发应用的大家交流经验。
本次演讲视频录制于QCon杭州2011

"伤得起"的云计算应用——对云端应用之架构的思考

2011年4月21日至22日是值得云计算从业者纪念的日子。Amazon的IaaS服务出现故障,导致许多商业网站的服务中断,影响非常严重。作为云计算用户,我们需要思考的是,如何保证即便在云服务不可用的情况,我们的应用架构仍然能够屹立不倒?本文正是站在云计算用户的角度试图探讨这一问题。

让交付的速度跟上思考的速度

12人的技术团队,4组刀片服务器,每月20亿的访问量,每日1次准时部署,99.9%的可用性。这可能吗?当然。想知道如何做的吗?百姓网将与您分享他们在DevOps实践过程中的经验和技巧。
本次演讲视频录制于QCon杭州2011

架构之路——穿行在产品和业务之间

篱笆作为一家起源于社区的电子商务公司,反映到技术层面就是同时要面对产品和业务,以及经营战略的变化调整。如何在产品和业务的夹缝之间完成技术架构的抽象与平衡,寻找更有效的价值定位,这当中有些经验教训和个人感悟愿与众人分享。
本次演讲视频录制于QCon杭州2011

特性注入:成功三部曲

本文将对特性注入以及相关方法做一个扫盲性的介绍。我们会解释这个框架的关键要素,并附上实例来证实它们。为了让文章保持相对较短,我们不会深入到某个工具或方法中,而是会给出一些参考资料,以便大家做进一步的研究。