Подтвердить что ты не робот

Параллельные методы в Rails

У моего веб-приложения My Rails есть десятки методов от вызовов к API и обработки результата запроса. Эти методы имеют следующую структуру:

def method_one
  batch_query_API
  process_data
end
..........
def method_nth
  batch_query_API
  process_data
end

def summary
  method_one
  ......
  method_nth
  collect_results
end

Как я могу запускать все методы запросов одновременно, а не последовательно в Rails (без необходимости запуска нескольких сотрудников, конечно)?

Изменить: все методы вызываются из одной переменной экземпляра. Я думаю, что это ограничивает использование Sidekiq или Delay при отправке заданий одновременно.

4b9b3361

Ответ 1

Ruby обладает отличным обещанием. Ваш пример будет выглядеть так:

require 'future'

def method_one
...
def method_nth

def summary
  result1 = future { method_one }
  ......
  resultn = future { method_nth }
  collect_results result1, ..., resultn
end

Прост, не так ли? Но позвольте получить более подробную информацию. Это будущий объект:

result1 = future { method_one }

Это означает, что result1 получает оценку в фоновом режиме. Вы можете передать его другим методам. Но result1 пока не имеет результата, он все еще обрабатывается в фоновом режиме. Подумайте о том, чтобы обойти нить. Но главное отличие - в тот момент, когда вы пытаетесь его прочитать, вместо того, чтобы передавать его, он блокирует и ждет результата в этот момент. Таким образом, в приведенном выше примере все переменные result1 .. resultn будут продолжать оцениваться в фоновом режиме, но когда придет время собирать результаты, и когда вы попытаетесь действительно прочитать эти значения, чтения будут ждать завершения запросов эта точка.

Установите драгоценный камень promise и попробуйте ниже в консоли Ruby:

require 'future'
x = future { sleep 20; puts 'x calculated'; 10 }; nil
# adding a nil to the end so that x is not immediately tried to print in the console
y = future { sleep 25; puts 'y calculated'; 20 }; nil

# At this point, you'll still be using the console!
# The sleeps are happening in the background

# Now do:
x + y
# At this point, the program actually waits for the x & y future blocks to complete

Изменить: Typo в result, должно быть result1, изменить echo на puts

Ответ 2

Вы можете взглянуть на новую опцию в городе: жемчужина Futoroscope. Как вы можете видеть из анонсирующего сообщения в блоге, он пытается решить ту же проблему, с которой вы сталкиваетесь, выполняя одновременные запросы API. Кажется, у него довольно хорошая поддержка и хорошее тестовое покрытие.

Ответ 3

Предполагая, что ваша проблема - медленный внешний API, решением может быть использование либо многопоточного программирования, либо асинхронного программирования. По умолчанию при выполнении ввода-вывода ваш код будет заблокирован. По сути, это означает, что если у вас есть метод, который выполняет HTTP-запрос для извлечения некоторого JSON, ваш метод сообщит вашей операционной системе, что вы собираетесь спать, и вы не хотите, чтобы его разбудили, пока операционная система не получит ответ на этот запрос. Поскольку это может занять несколько секунд, вашему приложению просто придется ждать.

Это поведение не относится только к HTTP-запросам. Чтение из файла или устройства, такого как веб-камера, имеет те же последствия. Программное обеспечение делает это, чтобы не перегружать процессор, когда он явно его не использует.

Итак, вопрос в вашем случае: нужно ли нам ждать завершения одного метода, прежде чем мы сможем вызвать другой? Если поведение метода method_two зависит от результата метода method_one, тогда да. Но в вашем случае кажется, что это отдельные единицы работы без взаимозависимости. Таким образом, существует потенциал для выполнения параллелизма.

Вы можете запускать новые потоки, инициализируя экземпляр класса Thread блоком, который содержит код, который вы хотите запустить. Думайте о потоке как о программе внутри вашей программы. Ваш интерпретатор Ruby будет автоматически переключаться между потоком и вашей основной программой. Вы можете запустить столько потоков, сколько захотите, но чем больше потоков вы создадите, тем дольше ваши основные программы будут ждать, прежде чем вернуться к выполнению. Тем не менее, мы, вероятно, говорим микросекунды или меньше. Давайте посмотрим на пример многопоточного исполнения.

def main_method
  Thread.new { method_one }
  Thread.new { method_two }
  Thread.new { method_three }
end

def method_one
  # something_slow_that_does_an_http_request
end

def method_two
  # something_slow_that_does_an_http_request
end

def method_three
  # something_slow_that_does_an_http_request
end

Вызов main_method вызовет выполнение всех трех методов в том, что кажется параллельным. В действительности они все еще обрабатываются последовательно, но вместо того, чтобы переходить в спящий режим, когда method_one блокируется, Ruby просто вернется в основной поток и переключится обратно в поток method_one, когда ОС подготовит ввод данных.

Предполагая, что каждому методу для выполнения требуется две 2 мс минус ожидание ответа, это означает, что все три метода запускаются через 6 мс - практически мгновенно.

Если мы предполагаем, что для ответа требуется 500 мс, это означает, что вы можете сократить общее время выполнения с 2 + 500 + 2 + 500 + 2 + 500 до 2 + 2 + 2 + 500 - другими словами, с 1506 мс до 506 мс.

Будет казаться, что методы работают одновременно, но на самом деле они просто спят одновременно.

В вашем случае, однако, у вас есть проблема, потому что у вас есть операция, которая зависит от завершения набора предыдущих операций. Другими словами, если у вас есть задачи A, B, C, D, E и F, то A, B, C, D и E могут выполняться одновременно, но F не может выполняться до A, B, C, D и E. все завершено.

Есть разные способы решить эту проблему. Давайте посмотрим на простое решение, которое создает сонный цикл в главном потоке, который периодически проверяет список возвращаемых значений, чтобы убедиться, что какое-то условие выполнено.

def task_1
# Something slow
return results
end

def task_2
# Something slow
return results
end

def task_3
# Something slow
return results
end

my_responses = {}
Thread.new { my_responses[:result_1] = task_1 }
Thread.new { my_responses[:result_2] = task_2 }
Thread.new { my_responses[:result_3] = task_3 }

while (my_responses.count < 3) # Prevents the main thread from continuing until the three spawned threads are done and have dumped their results in the hash.
  sleep(0.1) # This will cause the main thread to sleep for 100 ms between each check. Without it, you will end up checking the response count thousands of times pr. second which is most likely unnecessary.
end

# Any code at this line will not execute until all three results are collected.

Имейте в виду, что многопоточное программирование - сложная тема с многочисленными подводными камнями. С MRI это не так уж и плохо, потому что хотя MRI успешно переключается между заблокированными потоками, MRI не поддерживает одновременное выполнение двух потоков, и это решает довольно много проблем параллелизма.

Если вы хотите заняться многопоточным программированием, я рекомендую эту книгу: http://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601

Он сосредоточен вокруг Java, но объясненные подводные камни и концепции универсальны.

Ответ 4

Вы должны проверить Sidekiq.

RailsCasts эпизод о Sidekiq.