299

我知道 ruby​​ 使用绿色线程的“合作”线程。如何在我的应用程序中创建真正的“操作系统级”线程以利用多个 cpu 内核进行处理?

4

9 回答 9

615

更新了 Jörg 2011 年 9 月的评论

您似乎在这里混淆了两个非常不同的东西:Ruby 编程语言和 Ruby 编程语言的一种特定实现的特定线程模型。目前大约有 11 种不同的 Ruby 编程语言实现,具有非常不同和独特的线程模型。

(不幸的是,这 11 个实现中只有两个实际上已准备好用于生产,但到今年年底,这个数字可能会上升到四个或五个。)(更新:现在是 5 个:MRI、JRuby、YARV(解释器Ruby 1.9)、Rubinius 和 IronRuby)。

  1. 第一个实现实际上没有名字,这使得引用它很尴尬,而且真的很烦人和令人困惑。它最常被称为“Ruby”,这比没有名字更令人讨厌和困惑,因为它会导致 Ruby 编程语言的特性和特定的 Ruby 实现之间无休止的混淆。

    它有时也称为“MRI”(代表“Matz 的 Ruby 实现”)、CRuby 或 MatzRuby。

    MRI 在其解释器中将 Ruby 线程实现为绿色线程。不幸的是,它不允许并行调度这些线程,它们一次只能运行一个线程。

    但是,任何数量的 C 线程(POSIX 线程等)都可以与 Ruby 线程并行运行,因此外部 C 库或创建自己的线程的 MRI C 扩展仍然可以并行运行。

  2. 第二个实现是YARV(“Yet Another Ruby VM”的缩写)。YARV 将 Ruby 线程实现为 POSIX 或 Windows NT 线程,但是,它使用全局解释器锁 (GIL) 来确保在任何时候实际上只能调度一个 Ruby 线程。

    与 MRI 一样,C 线程实际上可以与 Ruby 线程并行运行。

    将来,GIL可能会分解成更细粒度的锁,从而允许越来越多的代码实际并行运行,但这还很遥远,甚至还没有计划

  3. JRuby 将 Ruby 线程实现为 Native Threads,其中 JVM 的“Native Threads”显然意味着“JVM Threads”。JRuby 没有对它们施加额外的锁定。因此,这些线程是否可以真正并行运行取决于 JVM:一些 JVM 将 JVM 线程实现为 OS 线程,而另一些则实现为绿色线程。(从 JDK 1.3 开始,Sun/Oracle 的主流 JVM 只使用 OS 线程)

  4. XRuby还将Ruby 线程实现为 JVM 线程更新:XRuby 已经死了。

  5. IronRuby 将 Ruby 线程实现为 Native Threads,其中 CLR 中的“Native Threads”显然意味着“CLR Threads”。IronRuby 没有对它们施加额外的锁定,因此,它们应该并行运行,只要您的 CLR 支持。

  6. Ruby.NET还将Ruby 线程实现为 CLR 线程更新: Ruby.NET 已经死了。

  7. Rubinius 在其虚拟机中将 Ruby 线程实现为绿色线程。更准确地说:Rubinius VM 导出了一个非常轻量级、非常灵活的并发/并行/非本地控制流构造,称为“任务”,以及所有其他并发构造(本讨论中的线程,还有ContinuationsActor和其他东西) 在纯 Ruby 中使用 Tasks 实现。

    但是,Rubinius 不能(当前)并行调度线程,但补充说这并不是什么大问题:Rubinius 已经可以在一个 Rubinius 进程中并行运行多个 POSIX 线程中的多个 VM 实例。由于线程实际上是在 Ruby 中实现的,因此它们可以像任何其他 Ruby 对象一样被序列化并发送到不同 POSIX 线程中的不同 VM。(这与 BEAM Erlang VM 用于 SMP 并发的模型相同。它已经为 Rubinius Actors 实现了。)

    更新:此答案中有关 Rubinius 的信息是关于 Shotgun VM 的,它不再存在。“新”C++ VM 不使用跨多个 VM 调度的绿色线程(即 Erlang/BEAM 样式),它使用具有多个本机操作系统线程模型的更传统的单个 VM,就像 CLR、Mono 所采用的模型一样,以及几乎每个 JVM。

  8. MacRuby最初是作为 YARV 在 Objective-C 运行时和 CoreFoundation 和 Cocoa 框架之上的一个端口。它现在与 YARV 明显不同,但 AFAIK 目前仍与 YARV 共享相同的线程模型更新: MacRuby 依赖于苹果垃圾收集器,该垃圾收集器已被声明为已弃用,并将在以后的 MacOSX 版本中删除,MacRuby 是不死的。

  9. CardinalParrot 虚拟机的 Ruby 实现。它还没有实现线程,但是,当它实现时,它可能会将它们实现为Parrot Threads更新:红衣主教似乎非常不活跃/死了。

  10. MagLevGemStone/S Smalltalk VM的 Ruby 实现。我不知道 GemStone/S 使用什么线程模型,MagLev 使用什么线程模型,或者即使线程已经实现(可能还没有)。

  11. HotRuby本身并不是一个完整的 Ruby 实现。它是 JavaScript 中 YARV 字节码 VM 的实现。HotRuby 不支持线程(还没有?),当它支持时,它们将无法并行运行,因为 JavaScript 不支持真正的并行性。不过,HotRuby 有一个 ActionScript 版本,而且 ActionScript 可能实际上支持并行性。更新:HotRuby 已经死了。

不幸的是,这 11 个 Ruby 实现中只有两个实际上是生产就绪的:MRI 和 JRuby。

所以,如果你想要真正的并行线程,JRuby 目前是你唯一的选择——这并不是一个坏的选择:JRuby 实际上比 MRI 更快,并且可以说更稳定。

否则,“经典” Ruby 解决方案是使用进程而不是线程来实现并行性。Ruby 核心库包含带有 方法的Process模块,这使得分叉另一个 Ruby 进程变得非常容易。此外,Ruby 标准库包含 分布式 Ruby (dRuby / dRb)库,它允许 Ruby 代码在多个进程中轻松分布,不仅在同一台机器上,而且在网络上。Process.fork

于 2008-09-11T22:25:12.903 回答
28

Ruby 1.8 只有绿色线程,没有办法创建真正的“OS 级”线程。但是,ruby 1.9 将有一个新特性,称为纤维,它允许您创建实际的操作系统级线程。不幸的是,Ruby 1.9 仍处于测试阶段,计划在几个月内稳定下来。

另一种选择是使用 JRuby。JRuby 将线程实现为操作系统级别的线程,其中没有“绿色线程”。JRuby的最新版本是1.1.4,相当于Ruby 1.8

于 2008-09-11T09:05:37.833 回答
8

这取决于实现:

  • MRI没有,YARV更接近。
  • JRuby 和 MacRuby 都有。




Ruby 有Blocks,lambdasProcs. 为了充分利用 JRuby 中的闭包和多核,Java 的执行器就派上用场了;对于 MacRuby,我喜欢GCD 的 queues

请注意,能够创建真正的“操作系统级”线程并不意味着您可以使用多个 cpu 内核进行并行处理。看看下面的例子。

这是使用 Ruby 2.1.0使用 3 个线程的简单 Ruby 程序的输出:

(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。您可以看到三个带有 state 的线程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 Under a Microscope是一本不错的读物。
此外, Omniref 中的Ruby 线程和 C 中的全局解释器锁在源代码中解释了为什么 Ruby 线程不能并行运行。

于 2014-02-03T16:00:41.660 回答
4

使用drb怎么样?它不是真正的多线程,而是多个进程之间的通信,但您现在可以在 1.8 中使用它,而且摩擦力相当低。

于 2008-09-11T11:57:27.797 回答
3

我会让“系统监视器”回答这个问题。在这两种情况下,我都在 i7(4 个超线程核心)机器上运行 8 个 Ruby 线程执行相同的代码(下面,它计算素数)......第一次运行是:

jruby 1.5.6 (ruby 1.8.7 patchlevel 249) (2014-02-03 6586) (OpenJDK 64-Bit Server VM 1.7.0_75) [amd64-java]

第二个是:

红宝石 2.1.2p95 (2014-05-08) [x86_64-linux-gnu]

有趣的是,JRuby 线程的 CPU 更高,但解释型 Rub​​y 的完成时间略短。从图中很难看出,但第二次(解释为 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
于 2015-02-22T23:07:52.480 回答
1

如果您使用 MRI,那么您可以在 C 中编写线程代码作为扩展或使用 ruby​​-inline gem。

于 2008-09-11T18:50:51.407 回答
1

如果您真的需要 Ruby 中的并行性来实现生产级系统(不能使用 beta 版),那么流程可能是更好的选择。
但是,绝对值得首先尝试 JRuby 下的线程。

此外,如果您对 Ruby 下线程的未来感兴趣,您可能会发现这篇文章很有用。

于 2008-09-11T19:10:36.257 回答
1

这是关于 Rinda 的一些信息,它是 Linda 的 Ruby 实现(并行处理和分布式计算范式)http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html

于 2010-01-01T00:24:29.403 回答
1

因为无法编辑那个答案,所以在这里添加一个新的回复。

更新(2017-05-08)

这篇文章很老了,信息不符合当前(2017)的步伐,以下是一些补充:

  1. Opal是一个 Ruby 到 JavaScript 源代码到源代码的编译器。它还有一个 Ruby corelib 的实现,它目前非常活跃,并且存在大量(前端)框架在它上面工作。并准备好生产。因为基于javascript,它不支持并行线程。

  2. truffleruby是 Ruby 编程语言的高性能实现。TruffleRuby 由 Oracle Labs 在 GraalVM 上构建,是 JRuby 的一个分支,将它与来自 Rubinius 项目的代码相结合,还包含来自 Ruby、MRI 的标准实现的代码,仍然是实时开发,而不是生产就绪。这个版本的 ruby​​ 似乎是为性能而生的,我不知道是否支持并行线程,但我认为应该。

于 2017-05-08T14:26:53.483 回答