ruby 有真正的多线程吗?
-
09-06-2019 - |
题
我知道 ruby 使用的“协作”线程 绿线. 。如何在应用程序中创建真正的“操作系统级”线程,以便利用多个 cpu 核心进行处理?
解决方案
更新了 Jörg 2011 年 9 月的评论
你似乎混淆了两个 非常 这里有不同的东西:这 Ruby编程语言及其具体线程模型之一 Ruby 编程语言的具体实现。那里 目前大约有 11 种不同的 Ruby 实现 编程语言,与 非常 不同且独特的线程 模型。
(不幸的是,这 11 个实现中只有两个实际上是 准备投入生产使用,但到今年年底,这个数字 可能会上升到四五个。(更新: :现在是 5:MRI、JRuby、YARV(Ruby 1.9 的解释器)、Rubinius 和 IronRuby)。
第一个实现实际上没有名称,它 提到它很尴尬,真的很烦人,而且 混乱。它通常被称为“Ruby”,它甚至 比没有名字更烦人和困惑,因为它 导致 Ruby 功能之间无休止的混淆 编程语言和特定的 Ruby 实现。
它有时也被称为“MRI”(用于“Matz's Ruby” 实现“)、CRuby 或 MatzRuby。
MRI将Ruby线程作为其解释器中的绿色线. 。不幸的是,它不允许这些线程 要并行调度,它们只能在 时间。
但是,可以运行任意数量的 C 线程(POSIX 线程等) 与 Ruby 线程并行,因此外部 C 库或 MRI 创建自己的线程的 C 扩展仍然可以在 平行。
第二个实现是 亚病毒 (“然而”的缩写 另一个 Ruby VM“)。 yarv将红宝石线程用作POSIX或Windows NT线程, ,但是,它使用全局解释器 锁定 (GIL) 以确保实际上只有一个 Ruby 线程 安排在任何时间。
像 MRI、C 线程 能 实际上与 Ruby 线程并行运行。
未来,GIL 有可能 可能 坏了 向下到更细粒度的锁,从而允许越来越多的 代码实际并行运行,但这太遥远了,它是 甚至没有 计划 然而。
JRuby 将 Ruby 线程实现为本机线程, 其中 JVM 中的“Native Threads”显然意味着“JVM 线程”。JRuby 不对它们强加额外的锁定。所以 这些线程是否真的可以并行运行取决于 JVM:一些 JVM 将 JVM 线程实现为操作系统线程,而一些 JVM 将 JVM 实现为 OS 线程 作为 Green Threads。(Sun/Oracle 的主流 JVM 自 JDK 1.3 起仅使用操作系统线程)
红宝石 还 将 Ruby 线程实现为 JVM 线程. 更新: :XRuby 死了。
铁红宝石 将 Ruby 线程实现为本机线程, 其中 CLR 中的“本机线程”显然意味着 “CLR 线程”。IronRuby 没有对它们施加额外的锁定, 因此,只要 CLR 支持,它们就应该并行运行 那。
Ruby.NET 还 将红宝石线程作为CLR线程. 更新: Ruby.NET 已死。
鲁比纽斯 将红宝石线作为绿色线程在其虚拟机中. 。更确切地说:鲁比尼乌斯 虚拟机导出非常轻量级,非常灵活 并发/并行/非本地控制流构造,称为 a "任务“,以及所有其他并发构造(线程 这次讨论,也 延续, 演员 和 其他东西)是用纯 Ruby 实现的,使用 Tasks。
Rubinius 不能(目前)并行调度线程, 但是,添加这并不是一个太大的问题:鲁比尼乌斯可以 已经 并行在几个POSIX线程中运行多个VM实例, ,在一个 Rubinius 过程中。由于线程是 实际上,它们可以像其他 Ruby 一样在 Ruby 中实现 对象,被序列化并发送到不同 POSIX 线程。(与 BEAM 相同型号 埃尔兰 虚拟机 用于 SMP 并发。已经是 为鲁比尼斯演员实施.)
更新: :此答案中有关 Rubinius 的信息是关于 Shotgun VM 的,该虚拟机已不存在。“新”C++ VM 不使用跨多个 VM 调度的绿色线程(即Erlang/BEAM 风格),它使用更传统的单个 VM 和多个本机操作系统线程模型,就像 CLR、Mono 和几乎所有 JVM 所使用的模型一样。
麦克鲁比 最初是 YARV 在 Objective-C 运行时以及 CoreFoundation 和 Cocoa 框架。它 现在已经与 YARV 显着背道而驰,但 AFAIK 它目前 还 与 YARV 共享相同的线程模型. 更新: MacRuby 依赖于苹果垃圾收集器,该垃圾收集器已被声明弃用,并将在 MacOSX 的后续版本中删除,MacRuby 是不死的。
红衣主教 是一个 Ruby 实现 鹦鹉虚拟机. 。然而,它还没有实现线程, 当它这样做时,它可能会将它们实现为 鹦鹉线. 更新: :红衣主教看起来非常不活跃/死气沉沉。
磁浮 是一个 Ruby 实现 宝石/S Smalltalk VM. 。我不知道什么线程模型 GemStone/S 使用,MagLev 使用什么螺纹模型,甚至 线程甚至还没有实现(可能还没有)。
热红宝石 是 不是 完整的 Ruby 实现 有。它是 YARV 字节码 VM 的实现 JavaScript的。HotRuby还不支持线程,以及何时支持 ,它们将无法并行运行,因为 JavaScript 不支持真正的并行性。有一个 ActionScript 然而,HotRuby 的版本和 ActionScript 实际上可能 支持并行性。 更新: :热鲁比死了。
不幸的是,这 11 个 Ruby 实现中只有两个是 实际生产就绪:MRI 和 JRuby。
所以,如果你想要真正的并行线程,JRuby目前是你的 唯一的选择——并不是说这是一个糟糕的选择:JRuby实际上更快 比 MRI 更稳定,可以说更稳定。
否则,“经典”的 Ruby 解决方案是使用进程
而不是用于并行性的线程。Ruby 核心库
包含 Process
模块 与 Process.fork
方法 这使得分叉另一个 Ruby 变得非常容易
过程。此外,Ruby 标准库还包含分布式 Ruby (dRuby / dRb) 库,允许 Ruby
代码将简单地分布在多个进程中,而不是
仅在同一台机器上,但也跨网络。
其他提示
Ruby 1.8 只有绿色线程,无法创建真正的“操作系统级”线程。但是,Ruby 1.9 将有一个称为 Fiber 的新功能,它允许您创建实际的操作系统级线程。不幸的是,Ruby 1.9 仍处于测试阶段,预计几个月后就会稳定下来。
另一种选择是使用 JRuby。JRuby 将线程实现为操作系统级主题,其中没有“绿色线程”。JRuby最新版本是1.1.4,相当于Ruby 1.8
这取决于实施:
- MRI没有,YARV更接近。
- JRuby 和 MacRuby 有。
红宝石有 关闭 作为 Blocks
, lambdas
和 Procs
. 。为了充分利用 JRuby 中的闭包和多核, Java的执行者 派上用场;对于 MacRuby 我喜欢 GCD 的队列.
请注意,能够创建 真正的“操作系统级”线程 并不意味着您可以使用多个 cpu 核心进行并行处理。看下面的例子。
这是的输出 一个使用 3 个线程的简单 Ruby 程序 使用 Ruby 2.1.0:
(jalcazar@mac ~)$ ps -M 69877
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 69877 s002 0.0 S 31T 0:00.01 0:00.04 /Users/jalcazar/.rvm/rubies/ruby-2.1.0/bin/ruby threads.rb
69877 0.0 S 31T 0:00.01 0:00.00
69877 33.4 S 31T 0:00.01 0:08.73
69877 43.1 S 31T 0:00.01 0:08.73
69877 22.8 R 31T 0:00.01 0:08.65
正如您在这里所看到的,有四个操作系统线程,但只有一个具有状态 R
在跑。这是由于 Ruby 线程的实现方式存在限制。
相同的程序,现在使用 JRuby。你可以看到三个线程的状态 R
, ,这意味着它们是并行运行的。
(jalcazar@mac ~)$ ps -M 72286
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 72286 s002 0.0 S 31T 0:00.01 0:00.01 /Library/Java/JavaVirtualMachines/jdk1.7.0_25.jdk/Contents/Home/bin/java -Djdk.home= -Djruby.home=/Users/jalcazar/.rvm/rubies/jruby-1.7.10 -Djruby.script=jruby -Djruby.shell=/bin/sh -Djffi.boot.library.path=/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jni:/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jni/Darwin -Xss2048k -Dsun.java.command=org.jruby.Main -cp -Xbootclasspath/a:/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jruby.jar -Xmx1924M -XX:PermSize=992m -Dfile.encoding=UTF-8 org/jruby/Main threads.rb
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 33T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.09 0:02.34
72286 7.9 S 31T 0:00.15 0:04.63
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.04 0:01.68
72286 0.0 S 31T 0:00.03 0:01.54
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.01 0:00.01
72286 0.0 S 31T 0:00.00 0:00.01
72286 0.0 S 31T 0:00.00 0:00.03
72286 74.2 R 31T 0:09.21 0:37.73
72286 72.4 R 31T 0:09.24 0:37.71
72286 74.7 R 31T 0:09.24 0:37.80
同样的程序,现在使用 MacRuby。还有三个线程并行运行。这是因为 MacRuby 线程是 POSIX 线程 (真正的“操作系统级”线程)并且有 无GVL
(jalcazar@mac ~)$ ps -M 38293
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 38293 s002 0.0 R 0T 0:00.02 0:00.10 /Users/jalcazar/.rvm/rubies/macruby-0.12/usr/bin/macruby threads.rb
38293 0.0 S 33T 0:00.00 0:00.00
38293 100.0 R 31T 0:00.04 0:21.92
38293 100.0 R 31T 0:00.04 0:21.95
38293 100.0 R 31T 0:00.04 0:21.99
再次执行相同的程序,但现在使用的是老式 MRI。由于此实现使用绿色线程,因此仅显示一个线程
(jalcazar@mac ~)$ ps -M 70032
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 70032 s002 100.0 R 31T 0:00.08 0:26.62 /Users/jalcazar/.rvm/rubies/ruby-1.8.7-p374/bin/ruby threads.rb
如果您对 Ruby 多线程感兴趣,您可能会找到我的报告 使用 fork 处理程序调试并行程序 有趣的。
有关 Ruby 内部结构的更全面的概述 显微镜下的红宝石 是一本好书。
还, Ruby 线程和 C 中的全局解释器锁 Omniref 在源代码中解释了为什么 Ruby 线程不并行运行。
怎么样使用 德布?它不是真正的多线程,而是多个进程之间的通信,但您现在可以在 1.8 中使用它,而且它的摩擦相当低。
这个问题就让“系统监视器”来回答吧。在这两种情况下,我都使用在 i7(4 个超线程核心)机器上运行的 8 个 Ruby 线程执行相同的代码(如下,计算素数)...第一次运行是:
JRuby 1.5.6 (Ruby 1.8.7 补丁级别 249) (2014-02-03 6586) (OpenJDK 64 位服务器 VM 1.7.0_75)[amd64-java]
第二个是:
红宝石 2.1.2p95 (2014-05-08) [x86_64-linux-gnu]
有趣的是,JRuby 线程的 CPU 较高,但解释的 Ruby 的完成时间稍短。从图中很难看出,但第二次(解释的 Ruby)运行使用了大约 1/2 的 CPU(没有超线程?)
def eratosthenes(n)
nums = [nil, nil, *2..n]
(2..Math.sqrt(n)).each do |i|
(i**2..n).step(i){|m| nums[m] = nil} if nums[i]
end
nums.compact
end
MAX_PRIME=10000000
THREADS=8
threads = []
1.upto(THREADS) do |num|
puts "Starting thread #{num}"
threads[num]=Thread.new { eratosthenes MAX_PRIME }
end
1.upto(THREADS) do |num|
threads[num].join
end
如果您使用 MRI,那么您可以用 C 语言编写线程代码作为扩展或使用 ruby-inline gem。
如果您确实需要 Ruby 中的并行性来实现生产级系统(您无法使用 Beta 版),那么流程可能是更好的选择。
但是,绝对值得首先尝试 JRuby 下的线程。
另外,如果您对 Ruby 下线程的未来感兴趣,您可能会发现这个 文章 有用。
以下是有关 Rinda 的一些信息,它是 Linda 的 Ruby 实现(并行处理和分布式计算范例) http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html
由于无法编辑该答案,因此在此处添加新回复。
更新(2017-05-08)
这篇文章很旧,信息不是最新的 (2017)胎面,以下是一些补充: