写点什么

Ruby 线程模型之未来

  • 2007-05-27
  • 本文字数:3703 字

    阅读完需:约 12 分钟

最近的一次对 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 (<br></br> blocking_func, /* function that that will block */<br></br> data, /* this will be passed above function */<br></br> unblock_func /* if another thread cause exception with Thread#raise,<br></br> this function is called to unblock or NULL */<br></br>)

问题在于:这样做有效地排除了对内核线程最大的赞成观点——对多核或者多 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 {<br></br> p "hello"<br></br> }或者也比这个 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

2007-05-27 19:001974
用户头像

发布了 117 篇内容, 共 13.6 次阅读, 收获喜欢 0 次。

关注

评论

发布
暂无评论
发现更多内容

你的城市有24小时共享自助洗车吗

自助洗车加盟

自助洗车加盟 自助洗车店

Move Protocol Beta测试版稳定,临时决定奖池规模再扩大

小哈区块

Move Protocol Beta测试版再调整,扩大总奖池

股市老人

自助洗车为什么可以做到24小时营业

自助洗车加盟

自助洗车机 自助洗车加盟 24小时自助洗车

数字藏品系统开发,NFT艺术品交易平台搭建

薇電13242772558

NFT 数字藏品

在线文档协作:办公必备高效率神器

小炮

如何使用 DATAX 以 UPSERT 语义更新下游 ORACLE 数据库中的数据

明哥的IT随笔

oracle 大数据 数据仓库 DataX

带你区分几种并行

华为云开发者联盟

后端 开发 华为云

Serverless 时代下微服务应用全托管解决方案

阿里巴巴云原生

阿里云 Serverless 微服务 云原生

Move Protocol Beta测试版稳定,临时决定奖池规模再扩大

西柚子

昇腾科研创新使能计划赋能开发者  华为计算提供三大维度支持

Geek_2d6073

华为云发布桌面IDE-CodeArts

华为云开发者联盟

云计算 开发工具 华为云 代码补全

揭秘支撑百度搜索、Feed、小程序三大业务的MVVM框架设计思想,San 核心人员倾力打造

图灵教育

前端开发 好书推荐 框架设计

天天在都在谈的S3协议到底是什么?一文带你了解S3背后的故事

wljslmz

对象存储 S3 6月月更

自助洗车一次费用不到10元你敢信

自助洗车加盟

自助洗车加盟 自助洗车费用

Gartner 网络研讨会 “九问数字化转型” 会后感

明哥的IT随笔

数字化转型

Move Protocol Beta测试版稳定,临时决定奖池规模再扩大

EOSdreamer111

深入剖析 HIVE 的锁和事务机制

明哥的IT随笔

大数据 hive 数据仓库

有哪些好用的工作汇报工具

优秀

低代码 工具软件

站在数字化风口,工装企业如何"飞起来"

华为云开发者联盟

云计算 低代码 开发 华为云

北京web前端培训 | React全家桶之入门介绍

@零度

React web前端开发

自助洗车到底有哪些不一样的地方

自助洗车加盟

自助洗车加盟 24小时自助洗车

自助洗车方便主要体现在哪方面

自助洗车加盟

自助洗车 自助洗车加盟

Move Protocol Beta测试版进行时,瓜分生态核心权益MOMO

鳄鱼视界

Docker入坑篇

青柚1943

Docker DevOps 云原生 容器化

秒云云原生信创全兼容解决方案再升级,助力信创产业加速落地

秒云

运维 云原生 信创 智能运维 信创云

Uniswap去中心化交易所系统开发方案

开发微hkkf5566

为 Serverless Devs 插上 Terraform 的翅膀,实现企业级多环境部署(上)

阿里巴巴云原生

阿里云 Serverless 云原生 开源项目

启动!阿里巴巴编程之夏2022

阿里巴巴云原生

阿里云 云原生 编程之夏

去中心化挖矿LP流动性DAPP系统开发案例

开发微hkkf5566

Ruby线程模型之未来_Ruby_Werner Schuster_InfoQ精选文章