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


当前回答

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

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

其他回答

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

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

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

这取决于实现:

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

Ruby有闭包block, lambdas和Procs。为了充分利用JRuby中的闭包和多核,Java的执行器派上了用场;对于MacRuby,我喜欢GCD的队列。 注意,能够创建真正的“操作系统级”线程并不意味着可以使用多个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 

正如您在这里看到的,有四个操作系统线程,但是只有状态为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 

同样的程序,但是现在用的是老式的核磁共振成像。由于这个实现使用绿色线程,所以只显示一个线程

(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线程不能并行运行。

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

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]

第二种是:

Ruby 2.2.1.p95 (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

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