Running threads inside my rails controller method

TheDelChop picture TheDelChop · Nov 18, 2013 · Viewed 7.8k times · Source

I've got a set of data that I'd like to do some calculations on inside my rails application, each calculation is independent of each other so I'd like to thread them so my response is much faster.

Here's what I've got ATM:

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

Now this returns a bunch of empty arrays for each of the member instances of @stats, however, if I join the threads together, it runs, but defeats the purpose of threading since each of the threads block.

Since I'm very new to threads, I'm wondering what I'm doing wrong here or if its even possible to accomplish what I'm trying do, that is, run 4 calcs in paralell and return the result to the client.

Thanks,

Joe

Answer

DiegoSalazar picture DiegoSalazar · Nov 19, 2013

It first depends if your calculations are doing processor heavy operations or are doing a lot blocking IO like reading from databases, the file system or the network. It wouldn't do much good if they're doing the former since each thread is taking up CPU time and no other thread can be scheduled - worse even if you're using Ruby MRI which has a Global Interpreter Lock. If the threads are doing blocking IO however, they can at least wait, let another thread run, wait, let another run and so on until they all return.

At the end you do have to join all the threads together because you want their return values. Do this below all your Thread.new calls. Save the return value of each Thread.new to an array:

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

Then join them together before you render:

threads.each &:join

If you want to really be sure this helps you out just benchmark the entire action:

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

Hope that helps.