Pregunta

I am trying to figure out how to make use of deferrables when it comes to long running computations that I have to implement on my own. For my example I want to calculate the first 200000 Fibonacci numbers but return only a certain one.

My first attempt of a deferrable looked like so:

class FibA
  include EM::Deferrable

  def calc m, n
    fibs = [0,1]
    i = 0

    do_work = proc{
      puts "Deferred Thread: #{Thread.current}"
      if i < m
        fibs.push(fibs[-1] + fibs[-2])
        i += 1
        EM.next_tick &do_work
      else
        self.succeed fibs[n]
      end
    }
    EM.next_tick &do_work
  end
end

EM.run do
  puts "Main Thread: #{Thread.current}"
  puts "#{Time.now.to_i}\n"

  EM.add_periodic_timer(1) do
    puts "#{Time.now.to_i}\n"
  end

  # calculating in reactor thread
  fib_a = FibA.new
  fib_a.callback do |x|
    puts "A - Result: #{x}"
    EM.stop
  end
  fib_a.calc(150000, 21)
end

Only to realize that everything seemed to work pretty well, but the thread the deferrable runs in is the same as the reactor thread (knowing that everything runs inside one system thread unless rbx or jruby are used). So I came up with a second attempt that seems nicer to me, especially because of different callback binding mechanism and the use of different threads.

class FibB
  include EM::Deferrable

  def initialize
    @callbacks = []
  end

  def calc m, n
    work = Proc.new do
      puts "Deferred Thread: #{Thread.current}"
      @fibs = 1.upto(m).inject([0,1]){ |a, v| a.push(a[-1]+a[-2]); a }
    end

    done = Proc.new do
      @callbacks.each{ |cb| cb.call @fibs[n]}
    end

    EM.defer work, done
  end

  def on_done &cb
    @callbacks << cb
  end
end

EM.run do
  puts "Main Thread: #{Thread.current}"
  puts "#{Time.now.to_i}\n"

  EM.add_periodic_timer(1) do
    puts "#{Time.now.to_i}\n"
  end

  # calculating in external thread
  fib_b = FibB.new
  fib_b.on_done do |res|
    puts "B - Result: #{res}"
  end
  fib_b.on_done do
    EM.stop
  end
  fib_b.calc(150000, 22)
end

Which one is the implementation that I should prefer? Are both wrong? Is there another, a better one?

Even more interesting: Is the second attempts a perfect way to implement whatever I want (except I/O op's) without blocking the reactor?

¿Fue útil?

Solución

Definitely EM.defer (or Thread.new I suppose), doing a long-running calculation in EM.next_tick will block your reactor for other things.

As a general rule, you don't want ANY block running inside reactor to be running for long regardless if it is or isn't IO blocking or the entire app halts while this is happening.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top