ruby是否有真正的multithreading?

我知道使用绿色线程的ruby的“合作” 线程 。 如何在我的应用程序中创build真正的“OS级”线​​程,以便利用多个CPU核心进行处理?

Jörg2011年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线程并行运行,所以创build自己的线程的外部C库或MRI C扩展仍然可以并行运行。

  2. 第二个实现是YARV (“又一个Ruby VM”)。 YARV将Ruby线程实现为POSIX或Windows NT线程 ,但是,它使用全局解释器锁(GIL)来确保一次只能调度一个Ruby线程。

    像MRI一样,C线程实际上可以平行于Ruby线程运行。

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

  3. JRuby将 Ruby线程作为本机线程来实现 ,其中JVM的“Native Threads”显然意味着“JVM线程”。 JRuby对它们没有额外的locking。 那么,这些线程是否可以实际并行运行取决于JVM:一些JVM将JVM线程实现为OS线程,一些实现为绿色线程。 (Sun / Oracle的主streamJVM自JDK 1.3开始使用OS线程)

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

  5. IronRuby将 Ruby线程作为本机线程来实现 ,其中CLR的“Native Threads”显然意味着“CLR线程”。 IronRuby不会对它们施加额外的locking,所以,只要你的CLR支持,它们应该并行运行。

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

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

    Rubinius不能(当前)并行地安排线程,但是,添加这个问题并不算太多:在一个Rubinius进程中,Rubinius可以在几个POSIX线程中并行运行多个VM实例 。 由于线程实际上是在Ruby中实现的,所以它们可以像任何其他Ruby对象一样被序列化并发送到另一个不同的POSIX线程中的VM。 (这与BEAM Erlang VM用于SMP并发的模型相同,Rubinius Actor已经实现了这个模型)。

    更新 :关于Rubinius在这个答案的信息是关于Shotgun虚拟机,不存在了。 “新”C ++虚拟机不使用跨多个虚拟机调度的绿色线程(即Erlang / BEAM风格),它使用了一个更传统的具有多个本地操作系统线程模型的单个虚拟机,就像CLR,Mono ,几乎每个JVM。

  8. MacRuby在Objective-C运行时和CoreFoundation和Cocoa框架的基础上开始成为YARV的一个端口。 现在它与YARV有明显的分歧,但是AFAIK目前仍然与YARV共享相同的线程模型 。 更新: MacRuby取决于苹果垃圾收集器,这是宣布废弃,并将在更高版本的MacOSX中删除,MacRuby是不死。

  9. Cardinal是Parrot虚拟机的Ruby实现。 它还没有实现线程,但是,它可能会将它们实现为Parrot线程 。 更新 :红衣主教看起来非常不活跃/死亡。

  10. MagLev是GemStone / S Smalltalk VM的Ruby实现。 我不知道GemStone / S使用什么线程模型,MagLev使用什么线程模型,甚至线程甚至实现了(可能不是)。

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

不幸的是,这11个Ruby实现中只有两个是实际可用的:MRI和JRuby。

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

否则,“经典的”Ruby解决scheme就是使用进程而不是线程进行并行处理。 Ruby Core库包含Process.fork方法的Process模块 ,这使得它很容易从另一个Ruby进程中分离出来。 此外,Ruby标准库还包含分布式Ruby(dRuby / dRb)库,它允许Ruby代码跨多个进程平分布,不仅在同一台机器上,而且在整个networking中。

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

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

这取决于实施:

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

Ruby已经closures了BlockslambdasProcs 。 为了充分利用JRuby中的closures和多核, Java的执行者能够派上用场; 对MacRuby我喜欢GCD的队列 。

请注意,能够创build真正的“OS级”线​​程并不意味着可以使用多个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一样的程序。 你可以看到三个状态为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线程 ( 真正的“OS级别”线程 )并且没有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 

再次,同样的程序,但现在与旧的RMI好。 由于这个实现使用绿色线程,所以只有一个线程出现

 (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 

如果您对Rubymultithreading感兴趣,您可能会发现我的报告使用fork处理程序debugging并行程序很有趣。
有关Ruby内部的更全面的概述Ruby在显微镜下是一个很好的阅读。
此外,Omniref中的Ruby线程和C中的全局解释器锁在源代码中解释了为什么Ruby线程不能并行运行。

如何使用drb ? 这不是真正的multithreading,而是几个进程之间的通信,但是现在可以在1.8版本中使用它,而且它的摩擦相当低。

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

jruby 1.5.6(ruby 1.8.7 patchlevel 249)(2014-02-03 6586)(OpenJDK 64位服务器虚拟机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 

如果您正在使用MRI,那么您可以用C语言编写线程代码,作为扩展名或使用ruby-inlinegem。

如果你真的需要Ruby中的并行性来生产级别的系统(你不能使用testing版),进程可能是更好的select。
但是,首先在JRuby下尝试线程是非常值得的。

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

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

因为无法编辑该答案,所以请在此处添加新答复。

更新(2017年5月8日)

这篇文章很旧,而且信息不是按照当前(2017年)的步伐,以下是一些补充:

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

  2. truffleruby是Ruby编程语言的高性能实现。 TrufleRuby是由Oracle实验室基于GraalVM构build的,它是JRuby的一个分支,将其与Rubinius项目的代码相结合,还包含Ruby,MRI的标准实现代码,仍然是实时开发,而不是生产准备。 这个版本的ruby好像是天生的performance,不知道是否支持并行线程,但我认为应该。