在我的Rails控制器方法中运行线程

12

我有一组数据想在我的Rails应用程序中进行一些计算,每个计算都互相独立,所以我希望将它们线程化以加快响应速度。

这是我目前的代码:

def show

  @stats = Stats.new

  Thread.new {
    @stats.top_brands = #RESULT OF FIRST CALCULATION     
  }

  Thread.new {
    @stats.top_retailers = #RESULT OF SECOND CALCULATION
  }

  Thread.new {
    @stats.top_styles = #RESULT OF THIRD CALCULATION
  }

  Thread.new {
     @stats.top_colors = #RESULT OF FOURTH CALCULATION
  }

  render json: @stats
end

现在,对于@stats的每个成员实例,都返回一组空数组。但是,如果我将这些线程合并在一起,它就可以运行,但这违反了线程的本意,因为每个线程都被阻塞。
由于我对线程非常陌生,我想知道我在这里做错了什么,或者我是否能够完成我试图做到的事情,即并行运行4个计算并将结果返回给客户端。
谢谢, Joe
1个回答

14

首先,这要取决于您的计算是进行处理器密集型操作还是进行大量阻塞IO,例如从数据库、文件系统或网络中读取。如果它们执行前者,则不会有太多好处,因为每个线程都占用CPU时间,而其他线程无法被调度——如果您使用的是Ruby MRI,情况甚至更糟,因为它具有全局解释器锁。但是,如果线程正在执行阻塞IO,则可以等待一段时间,让另一个线程运行,然后再等待,让另一个线程运行,直到所有线程都返回。

最后,您需要将所有线程组合在一起,因为您想要它们的返回值。在所有Thread.new调用之下执行此操作。将每个Thread.new的返回值保存到数组中:

threads = []
threads << Thread.new ...

然后在渲染之前将它们连接起来:

threads.each &:join

如果你想确保这真的有帮助,就对整个操作进行基准测试:

def show
  start_time = Time.now.to_f
  @stats = Stats.new

  Thread.new {
    @stats.top_brands = #RESULT OF FIRST CALCULATION     
  }
  Thread.new {
     @stats.top_colors = #RESULT OF FOURTH CALCULATION
  }

  @elapsed_time = Time.now.to_f - start_time
  # do something with @elapsed_time, like putsing it or rendering it in your response

  render json: @stats
end

希望这有所帮助。


没错,但为了让它真正起作用,我必须使用像JRuby这样的东西,对吧? - TheDelChop
如果你的线程中的计算正在进行阻塞IO,你仍然会看到加速。 这使它们进入睡眠状态,其他线程可以被调度开始他们的工作。 您可以像这样查看其工作原理:https://gist.github.com/DiegoSalazar/7547566#file-sleepy_threads-rb 确定最快的方法是使用和不使用线程对您的操作进行基准测试。 - DiegoSalazar
然而,在JRuby、Rubinius和Ruby EE中会更好,因为它们能够利用您机器上的所有核心来运行本地线程。 - DiegoSalazar
假设我在每个线程中连接到一个mongodb实例并处理一些数据。如果我理解正确,这是非阻塞I/O,对吗?因此,即使在MRI中,我也应该看到加速。 - TheDelChop
我不确定您使用的客户端库连接到mongodb时是否执行阻塞或非阻塞IO。但是它是一个典型的阻塞式网络操作。就像我说的,您应该使用线程和不使用线程来进行基准测试,以便更好地了解情况。 - DiegoSalazar
你说在线程内进行数据库调用时,完成后必须关闭连接。你能详细说明一下吗? - Donato

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