1

我正在尝试使用赛璐珞异步处理一些 .csv 数据。我已经读过,使用期货可以让您在主线程终止之前等待一组参与者完成。我看过一些例子来证明这一点。

但是,当我在示例代码中实现它时,事实证明使用期货并不比同步处理快。谁能看到我做错了什么?

require 'smarter_csv'
require 'celluloid/current'
require 'benchmark'

class ImportActor
  include Celluloid

  def process_row(row)
    100000.times {|n| n}
  end

end

def do_all_the_things_with_futures
  pool    = ImportActor.pool(size: 10)
  SmarterCSV.process("all_the_things.csv").map do |row|
    pool.future(:process_row,row)
  end.map(&:value)
end


def do_all_the_things_insync
  pool    = ImportActor.pool(size: 10)
  SmarterCSV.process("all_the_things.csv") do |row|
    pool.process_row(row)
  end
end

puts Benchmark.measure { do_all_the_things_with_futures}
puts Benchmark.measure { do_all_the_things_insync }

2.100000 0.030000 2.130000 ( 2.123381)

2.060000 0.020000 2.080000 ( 2.069357)

【4.6s完成】

4

1 回答 1

2

您使用的是标准的 ruby​​ MRI 解释器吗?

如果是这样,对于完全受 CPU 限制的任务(即不执行任何 I/O,但完全在 CPU 中执行计算的任务),您将不会获得任何加速。您的“测试”任务100000.times {|n| n}确实完全受 CPU 限制。

对于 MRI 上完全受 CPU 限制的任务,您不会通过多线程获得任何加速的原因是,MRI 解释器具有“全局解释器锁定”(GIL),它可以防止多个 CPU 内核ruby 解释器立即使用。多线程并行性,就像赛璐珞给你的那样,只能通过在不同的 CPU 内核上同时运行不同的线程来加速 CPU 工作,就像现在大多数系统一样,在多核系统上。

但在 MRI 中,这是不可能的。这是 ruby​​ MRI 解释器的限制。

如果您安装 JRuby 并在 JRuby 下运行测试,您应该会看到加速。

如果您的任务涉及一些 I/O(例如进行数据库查询,或等待远程 HTTP API,或进行大量文件读取或写入),您还可以在 MRI 下看到一些加速。您的任务花费在 I/O 上的时间越多,速度就越快。这是因为即使 MRI 不允许线程同时在多个 CPU 内核上执行,一个等待 I/O 的线程仍然可以被切换出去,而另一个线程切换进来工作。然而,如果您不使用线程,程序将只是坐在那里等待 I/O 不做任何工作。

如果你在谷歌上搜索“ruby GIL”,你可以找到更多关于这个问题的讨论。

如果你真的在做 CPU 密集型的工作,可以从多线程并行中受益,这对你的程序有很大帮助,考虑切换到 Jruby。

如果你真的需要多线程并行,使用赛璐珞的替代方法是使用来自concurrent-ruby包的 Futures 或 Promises。Concurrent-ruby 通常在内部比赛璐珞更简单且重量更轻。但是,无论您使用哪种工具,编写多线程代码都可能很棘手,即使您使用赛璐珞或 ruby​​-concurrent 为您提供比直接使用线程更好的更高级别的抽象,使用多线程并发也需要成为熟悉一些技术,不时需要一些棘手的调试。

于 2015-10-13T11:56:32.283 回答