红宝石有真正的多线程吗?


Answers:


612

随约尔格(Jörg)2011年9月评论而更新

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

(不幸的是,实际上这11种实现中只有两种可以用于生产,但是到今年年底,这个数字可能会增加到4到5种。)(更新:现在是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(“另一个Ruby VM”的缩写)。YARV将Ruby线程实现为POSIX或Windows NT线程,但是,它使用全局解释器锁(GIL)来确保实际上一次只能调度一个Ruby线程。

    MRI一样,C线程实际上到Ruby线程并行运行。

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

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

  4. XRuby还将Ruby Threads实现为JVM Threads更新:XRuby已死。

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

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

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

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

    更新:此答案中有关Rubinius的信息与Shotgun VM有关,后者不再存在。“新的” C ++ VM不使用跨多个VM调度的绿色线程(即Erlang / BEAM风格),它使用具有多个本机OS线程模型的更传统的单个VM,就像CLR,Mono所采用的那样,几乎每个JVM。

  8. MacRuby最初是在Objective-C运行时,CoreFoundation和Cocoa框架之上的YARV移植。现在它与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不支持真正的并行性。但是,有一个ActionScript版本的HotRuby,而ActionScript实际上可能支持并行性。更新:HotRuby已死。

不幸的是,这11个Ruby实现中只有两个实际上可以用于生产环境:MRI和JRuby。

因此,如果您想要真正的并行线程,那么JRuby当前是您唯一的选择-并不是一个坏选择:JRuby实际上比MRI更快,并且可以说更稳定。

否则,“经典” Ruby解决方案是使用进程而不是线程进行并行化。Ruby Core Library包含带有 方法Process模块,这使得分派另一个Ruby进程变得非常容易。此外,Ruby标准库包含 分布式Ruby(dRuby / dRb)库,该库允许将Ruby代码轻松地分布在多个进程中,不仅在同一台计算机上,而且在整个网络上。Process.fork


1
但使用fork会破坏jruby的用法...只是说
akostadinov 2014年

1
这是一个很好的答案。但是,它受到很多链接腐烂的影响。我不知道这些资源可能移到了哪里。
BlackVegetable

28

Ruby 1.8仅具有绿色线程,无法创建真正的“ OS级”线​​程。但是,ruby 1.9将具有称为纤维的新功能,该功能将允许您创建实际的OS级线程。不幸的是,Ruby 1.9仍处于beta中,计划在几个月内保持稳定。

另一种选择是使用JRuby。JRuby将线程实现为OS级主题,其中没有“绿色线程”。JRuby的最新版本是1.1.4,并且等效于Ruby 1.8


35
Ruby 1.8只有绿色线程是错误的,Ruby 1.8的几种实现都有本机线程:JRuby,XRuby,Ruby.NET和IronRuby。光纤不允许创建本机线程,它们比线程轻巧。它们实际上是半协程,即它们是合作的。
约尔格W¯¯米塔格

19
我认为从Josh的回答中可以很明显地看出,当他说Ruby 1.8时,他的意思是运行时是Ruby 1.8,即MRI,而不是Ruby 1.8。
Theo

@Theo很明显,他在回答中弄乱了概念。光纤不是创建本地线程的方法,正如已经提到的,它们比线程更轻巧,当前的crby具有本地线程但带有GIL。
Foo Bar Zoo

8

这取决于实现:

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




Ruby有倒闭BlockslambdasProcs。为了充分利用JRuby中的闭包和多个内核,Java的执行器派上了用场。对于MacRuby,我喜欢GCD的队列

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

这是一个简单的Ruby程序的输出,该程序在 Ruby 2.1.0中使用3个线程

(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 

如您所见,有四个OS线程,但是只有一个处于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线程真正的“ 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 


再次,相同的程序,但现在具有良好的旧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
另外,Omniref 中的C中的Ruby Threads和Global Interpreter Lock在源代码中解释了为什么Ruby线程不能并行运行。


RMI是指MRI?
Mayuresh Srivastava

4

使用drb怎么?它不是真正的多线程,而是几个进程之间的通信,但是您现在可以在1.8中使用它,并且摩擦很小。


3

我将让“系统监视器”回答这个问题。在两种情况下,我都使用在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

1

如果使用MRI,则可以将C中的线程代码编写为扩展代码或使用ruby-inline gem。


1

如果您真的需要在Ruby中为生产级系统(不能使用Beta)进行并行处理,那么处理可能是一个更好的选择。
但是,绝对值得首先在JRuby下尝试线程。

另外,如果您对将来在Ruby下进行线程化感兴趣,则可能会发现本文很有用。


JRuby是一个不错的选择。对于使用进程的并行处理,我喜欢 github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
2014年


1

由于无法编辑该答案,因此请在此处添加新的回复。

更新(2017-05-08)

本文非常古老,并且信息未遵循当前(2017)的规定,以下为一些补充:

  1. Opal是从Ruby到JavaScript的源到源编译器。它还有一个Ruby corelib的实现。它当前非常活跃,已经开发了,并且存在大量的(前端)框架。准备生产。由于基于javascript,因此不支持并行线程。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.