Ruby并发I/O

5

接下来,Ruby线程限制 - 也适用于任何语言

我正在尝试了解为什么我的线程无法工作。 一些答案非常清晰:

"..用fork创建4个子进程将利用您的4个内核" 这将是我的最终方法,因为在我的情况下,线程似乎不起作用。

还有这个:

"..Ruby MRI线程本身无法完全利用运行Ruby代码的多核CPU。但是,这对您是否成为问题取决于线程正在做什么。如果它们正在对同一台机器上的其他进程进行长时间运行的I / O调用,则无需单独进程即可获得好处。线程和多处理作为主题可能会变得相当复杂,即使是简单的事情。大多数语言都会在易于使用和难以处理的方面做出一些妥协..."

考虑到第二个,我已从代码中删除了所有处理并仅留下了I / O。

这就是它:

beginning_time = Time.now
img_processor.load_image(frames_dir+"/frame_0001.png")
img_processor.load_image(frames_dir+"/frame_0002.png")
end_time = Time.now
puts "Time elapsed #{(end_time - beginning_time)*1000} milliseconds"

beginning_time = Time.now
for frame_index in 1..2
    greyscale_frames_threads << Thread.new(frame_index) { |frame_number| 
        puts "Loading Image #{frame_number}"
        img_processor.load_image(frames_dir+"/frame_%04d.png"%+frame_number)
    }
end

puts "Joining Threads"
greyscale_frames_threads.each { |thread| thread.join } #this blocks the main thread
end_time = Time.now
puts "Time elapsed #{(end_time - beginning_time)*1000} milliseconds"

我拿到的是这个:
对于第一个非线程化情况:
时间经过了15561.358毫秒
对于第二个线程化情况:
时间流逝了15442.401毫秒
好的,性能提升在哪里?我错过了什么吗? 硬盘是否阻塞? 我真的需要产生进程才能在ruby中看到真正的并行吗?

1
既然你特别问到了I/O,那么可以通过使用异步I/O来实现无需线程的并发I/O。可以考虑使用EventMachine来实现。 - igorw
也许还有Celluloid::IO项目。 - Kashyap
1个回答

7

我真的需要在Ruby中生成进程才能看到真正的并行运算吗?

我认为是的:

require 'timeout'
require 'digest'
require 'benchmark'

def do_stuff
  Digest::SHA256.new.digest "a" * 100_000_000
end

N = 10
Benchmark.bm(10) do |x|

  x.report("sequential") do
    N.times do
      do_stuff
    end
  end

  x.report("subprocess") do
    N.times do
      fork { do_stuff }
    end
    Process.waitall
  end

  x.report("thread") do
    threads = []
    N.times do
      threads << Thread.new { do_stuff }
    end
    threads.each(&:join)
  end

end

MRI 2.0.0 的结果:

                 user     system      total        real
sequential   3.200000   0.180000   3.380000 (  3.383322)
subprocess   0.000000   0.000000   6.600000 (  1.068517)
thread       3.290000   0.210000   3.500000 (  3.496207)

第一个块(顺序)连续运行4次do_stuff,第二个块(子进程)在4个核上运行,而第三个块(线程)在1个核上运行。
如果您将do_stuff更改为:
def do_stuff
  sleep(1)
end

结果是不同的:
                 user     system      total        real
sequential   0.000000   0.000000   0.000000 ( 10.021893)
subprocess   0.000000   0.010000   0.080000 (  1.013693)
thread       0.000000   0.000000   0.000000 (  1.003463)

我猜我们现在谈论的是并行而不是并发。在我的脑海中,我将线程与并发相关联,将进程与并行性相关联。我仍然对http://stackoverflow.com/questions/17188065/ruby-thread-limit-also-for-any-language中的问题感到困惑,请查看kristofv的答案。Frederick Cheung说,kristofv的答案没有问题,这取决于你的线程所做的工作类型...因此,线程无法真正实现并行性。你需要小心告诉它们要做什么。 - Trt Trt
当然,你可以使用线程来实现并行处理。但是有一些 Ruby 实现无法同时运行多个线程。显然,如果你在其中之一上运行代码,就不会看到任何性能提升。 - Jörg W Mittag
2
你不需要等待所有的子进程完成吗?你在循环中调用了多个fork,所以使用Process.waitall可能是更好的选择。 - davissp14
回复@JörgWMittag的评论:那么在无法并行运行线程的情况下,例如MRI中,使用线程是否有意义?我认为根据程序的类型仍然有提高性能的空间,即当一个线程被I/O阻塞时,另一个线程可以接管执行。 - sixty4bit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接