Я пытаюсь понять, как использовать deferrables, когда дело доходит до длительных вычислений, которые я должен реализовать самостоятельно. В моем примере я хочу вычислить первые 200 000 чисел Фибоначчи, но вернуть только определенное.
Моя первая попытка deferrable выглядела так:
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
Только чтобы понять, что все, казалось, работает довольно хорошо, но поток, в котором работает отложенный, такой же, как поток реактора (зная, что все выполняется внутри одного системного потока, если не используются rbx или jruby). Поэтому я придумал вторую попытку, которая кажется мне более приятной, особенно из-за другого механизма привязки обратного вызова и использования разных потоков.
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
Какую реализацию мне следует предпочесть? Оба неправы? Есть ли другой, лучший?
Еще более интересно: является ли вторая попытка идеальным способом реализовать все, что я хочу (кроме операций ввода-вывода) без блокировки реактора?
defer
заблокирует ваше приложение, но не более чем на 10 мс каждый раз, когда планировщик потоков будет разблокировать его.defer
— хороший выбор для блокировки ввода-вывода (т. е. системных вызовов), потому что они освобождают GIL, а ваши вычисления — нет.next_tick
реализация вообще не должна блокироваться надолго. - person fl00r   schedule 19.11.2013