ruby 有真正的多线程吗?
Posted
技术标签:
【中文标题】ruby 有真正的多线程吗?【英文标题】:Does ruby have real multithreading? 【发布时间】:2010-09-08 12:54:12 【问题描述】:我知道使用 green threads 的 ruby 的“合作”线程。如何在我的应用程序中创建真正的“操作系统级”线程以利用多个 cpu 内核进行处理?
【问题讨论】:
【参考方案1】:更新了 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 implements Ruby Threads as Green Threads within its interpreter。不幸的是,它不允许那些线程 要并行调度,它们只能在一个线程中运行一个线程 时间。
但是,可以运行任意数量的 C 线程(POSIX 线程等) 与 Ruby 线程并行,因此外部 C 库或 MRI 创建自己的线程的 C 扩展仍然可以在其中运行 并行。
第二个实现是YARV(“Yet 另一个 Ruby VM")。YARV implements Ruby Threads as POSIX or Windows NT Threads,但是,它使用全局解释器 锁 (GIL) 以确保实际上只能有一个 Ruby 线程 安排在任何时间。
像 MRI 一样,C 线程可以实际上与 Ruby 线程并行运行。
在未来,GIL 可能被破坏是可能的 向下进入更细粒度的锁,从而允许越来越多的 实际并行运行的代码,但这太远了,它是 甚至还没有计划。
JRubyimplements Ruby Threads as Native Threads, 在 JVM 的情况下,“本机线程”显然意味着“JVM 线程”。JRuby 没有对它们施加额外的锁定。所以, 这些线程是否可以真正并行运行取决于 JVM:一些 JVM 将 JVM 线程实现为 OS 线程,还有一些 作为绿线。 (从 JDK 1.3 开始,Sun/Oracle 的主流 JVM 只使用 OS 线程)
XRuby 和 implements Ruby Threads as JVM Threads。 更新:XRuby 已死。
IronRubyimplements Ruby Threads as Native Threads, 在 CLR 的情况下,“本机线程”显然意味着 “CLR 线程”。 IronRuby 没有对它们施加额外的锁定, 因此,只要您的 CLR 支持,它们就应该并行运行 那个。
Ruby.NET 和 implements Ruby Threads as CLR Threads。 更新: Ruby.NET 已死。
Rubiniusimplements Ruby Threads as Green Threads within its Virtual Machine。更准确地说:鲁比尼乌斯 VM 导出非常轻量级,非常灵活 并发/并行/非本地控制流构造,称为 一个“Task”,以及所有其他并发构造(线程在 这个讨论,还有Continuations、Actors和 其他东西)在纯 Ruby 中实现,使用任务。
Rubinius 不能(当前)并行调度线程, 然而,补充说这并不是什么大问题:Rubinius 可以 已经run several VM instances in several POSIX Threads in parallel,在一个 Rubinius 进程中。由于线程是 实际上在 Ruby 中实现,它们可以像任何其他 Ruby 对象,被序列化并发送到不同的虚拟机 POSIX 线程。 (这与 BEAM Erlang VM 的型号相同 用于 SMP 并发。已经是implemented for Rubinius Actors。)
更新:此答案中有关 Rubinius 的信息是关于 Shotgun VM 的,它不再存在。 “新”C++ VM 不使用跨多个 VM 调度的绿色线程(即 Erlang/BEAM 样式),它使用具有多个本机操作系统线程模型的更传统的单个 VM,就像 CLR、Mono 所采用的模型一样,以及几乎所有 JVM。
MacRuby 最初是 YARV 的一个端口,位于 Objective-C 运行时和 CoreFoundation 和 Cocoa 框架。它 现在已经与 YARV 明显不同,但目前 AFAIK 还是shares the same Threading Model with YARV。 更新:MacRuby 依赖于 apples 垃圾收集器,该垃圾收集器已被声明为已弃用,并将在更高版本的 MacOSX 中删除,MacRuby 已死。
Cardinal 是 Parrot Virtual Machine 的 Ruby 实现。但是,它还没有实现线程, 当它这样做时,它可能会将它们实现为Parrot Threads。 更新:红衣主教似乎非常不活跃/死了。
MagLev 是 GemStone/S Smalltalk VM 的 Ruby 实现。我不知道什么线程模型 GemStone/S 使用什么线程模型 MagLev 或者即使 甚至还实现了线程(可能还没有)。
HotRuby 不是一个完整的 Ruby 实现 自己的。它是一个 YARV 字节码 VM 的实现 javascript。 HotRuby 不支持线程(还没有?)以及何时支持 确实,它们将无法并行运行,因为 JavaScript 不支持真正的并行性。有一个动作脚本 然而,HotRuby 版本和 ActionScript 可能实际上 支持并行。 更新:HotRuby 已死。
不幸的是,这 11 个 Ruby 实现中只有两个是 实际生产就绪:MRI 和 JRuby。
所以,如果你想要真正的并行线程,JRuby 目前是你的 唯一的选择——这并不是一个糟糕的选择:JRuby 实际上更快 比 MRI 更稳定。
否则,“经典”的 Ruby 解决方案是使用进程
而不是线程的并行性。 Ruby 核心库
包含 Process
module 和 Process.fork
method 这使得分叉另一个 Ruby 变得非常容易
过程。此外,Ruby 标准库包含
Distributed Ruby (dRuby / dRb) 库,它允许 Ruby
代码可以简单地分布在多个进程中,而不是
只能在同一台机器上,也可以跨网络。
【讨论】:
但是使用 fork 会破坏 jruby 的使用...只是说 这是一个很好的答案。但是,它会受到很多链接腐烂的影响。不过我不知道这些资源可能转移到了哪里。【参考方案2】:Ruby 1.8 只有绿色线程,没有办法创建真正的“操作系统级”线程。但是,ruby 1.9 将有一个新的特性,称为纤程,它允许您创建实际的操作系统级线程。不幸的是,Ruby 1.9 仍处于测试阶段,计划在几个月内稳定。
另一种选择是使用 JRuby。 JRuby 将线程实现为操作系统级别的线程,其中没有“绿色线程”。 JRuby的最新版本是1.1.4,相当于Ruby 1.8
【讨论】:
Ruby 1.8 只有绿色线程是错误的,Ruby 1.8 的几个实现有本地线程:JRuby、XRuby、Ruby.NET 和 IronRuby。纤维不允许创建本地线程,它们比线程更轻量。它们实际上是半协程,即它们是合作的。 我认为 Josh 的回答很明显,当他说 Ruby 1.8 时,他的意思是 Ruby 1.8 运行时,也就是 MRI,而不是 Ruby 1.8 语言。 @Theo 很明显,他在回答中混淆了概念。纤维不是一种创建原生线程的方法,正如已经提到的,它们比线程更轻量级,当前的 cruby 有原生线程,但使用 GIL。【参考方案3】:这取决于实现:
MRI 没有,YARV 更接近。 JRuby 和 MacRuby 都有。
Ruby 有 closures 和 Blocks
、lambdas
和 Procs
。要充分利用 JRuby 中的闭包和多核,Java's executors 会派上用场;对于 MacRuby,我喜欢 GCD's queues。
请注意,能够创建真正的“操作系统级”线程并不意味着您可以使用多个 cpu 内核进行并行处理。看看下面的例子。
这是a simple Ruby program which uses 3 threads 使用 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 threads are POSIX threads(真正的“操作系统级”线程)并且有no 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 多线程感兴趣,您可能会发现我的报告 Debugging parallel programs using fork handlers 很有趣。 对于 Ruby 内部结构的更一般的概述,Ruby Under a Microscope 是一本不错的读物。 此外,Omniref 中的Ruby Threads and the Global Interpreter Lock in C 在源代码中解释了为什么 Ruby 线程不能并行运行。
【讨论】:
RMI 是指 MRI?【参考方案4】:使用drb怎么样?它不是真正的多线程,而是多个进程之间的通信,但您现在可以在 1.8 中使用它,而且摩擦力相当低。
【讨论】:
【参考方案5】:我会让“系统监视器”回答这个问题。在这两种情况下,我都在 i7(4 个超线程核心)机器上运行 8 个 Ruby 线程执行相同的代码(如下,计算素数)...第一次运行是:
jruby 1.5.6 (ruby 1.8.7 补丁级别 249) (2014-02-03 6586) (OpenJDK 64-Bit Server VM 1.7.0_75) [amd64-java]
第二个是:
ruby 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
【讨论】:
【参考方案6】:如果您使用 MRI,那么您可以使用 C 语言编写线程代码作为扩展或使用 ruby-inline gem。
【讨论】:
【参考方案7】:如果您真的需要 Ruby 中的并行性来实现生产级系统(您无法使用 beta 版),那么流程可能是更好的选择。 但是,绝对值得首先尝试 JRuby 下的线程。
此外,如果您对 Ruby 下线程的未来感兴趣,您可能会发现这个 article 很有用。
【讨论】:
JRuby 是一个不错的选择。对于使用我喜欢的进程进行并行处理github.com/grosser/parallelParallel.map(['a','b','c'], :in_processes=>3)...
【参考方案8】:
这里有一些关于 Rinda 的信息,它是 Linda 的 Ruby 实现(并行处理和分布式计算范式)http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html
【讨论】:
【参考方案9】:因为无法编辑那个答案,所以在这里添加一个新的回复。
更新(2017-05-08)
这篇文章很老了,信息跟不上 (2017)踏步,以下是一些补充:
Opal 是一个 Ruby 到 JavaScript 源到源编译器。它还有一个 Ruby corelib 的实现,它目前非常活跃,并且存在大量的(前端)框架在它上面工作。 并准备好生产。因为基于javascript,所以不支持并行线程。
truffleruby 是 Ruby 编程语言的高性能实现。 TruffleRuby 由 Oracle Labs 在 GraalVM 上构建,是 JRuby 的一个分支,将它与来自 Rubinius 项目的代码相结合,还包含来自 Ruby、MRI 的标准实现的代码,仍然是实时开发,而不是生产就绪。 这个版本的 ruby 似乎是为性能而生的,我不知道是否支持并行线程,但我认为应该。
【讨论】:
以上是关于ruby 有真正的多线程吗?的主要内容,如果未能解决你的问题,请参考以下文章