2013-05-14 2 views
17

В моем приложении «Мои рельсы» есть десятки методов от вызовов к API и обработки запроса. Эти методы имеют следующую структуру:Методы параллелизации в Rails

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 при одновременном представлении заданий.

+1

Вы хотите http://celluloid.io/, я думаю –

ответ

22

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

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 камень и попробуйте ниже в Рубине консоли:

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 

Edit: опечатка в result, должны были result1, изменить echo к puts

+0

Элегантное решение! Я вдохновлен снова взглянуть на «обещание». – davogones

+0

@RDX: Кажется, это очень крутое решение. Это работает с одним объектом, который вызывает все методы? – AdamNYC

+1

@AdamNYC, да, он работает на уровне блока/функции, а не на уровне объекта. Это означает, что вы можете распараллелить любую часть кода. (Изменить: Случайно нажал ввод) – Subhas

0

Вы должны зарегистрироваться Sidekiq.

RailsCasts эпизод о Sidekiq.

+0

Я использую Sidekiq, но все эти методы вызывают из одной переменной экземпляра. Не могли бы вы объяснить, как вы это сделаете с Sidekiq? – AdamNYC

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, но подводные камни и понятия объясняется являются универсальными.

+0

Не могли бы вы объяснить, как я могу реализовать многопоточное программирование в приложении Rails? Имеет ли значение, если все мои методы нужно вызывать из объекта? – AdamNYC

+0

Нет, это не так. Тем не менее, многопоточное программирование представляет собой фундаментальное изменение в структуре структуры вашей программы, поэтому сначала вам нужно вложить некоторое время в понимание этого, прежде чем вы сможете создать многопоточное решение вашей проблемы. Это должно дать вам начало: http://www.tutorialspoint.com/ruby/ruby_multithreading.htm –

+1

Отличный ответ! Тем не менее, не будет ли ваш образец кода вечным, если один из потоков выйдет из строя и не сможет добавить запись в 'my_responses'? Обычно я повторяю потоки и «присоединяюсь» ко всем. – davogones

4

Вы можете взглянуть на новый вариант в городе: The futoroscope gem. Как вы можете видеть по объявлению blog post, он пытается решить ту же проблему, с которой вы сталкиваетесь, делая одновременный запрос API. Кажется, у него довольно хорошая поддержка и хорошее тестирование.

+0

Спасибо Пауло. Это очень полезно. – AdamNYC

Смежные вопросы