在Rails中并行化方法

我的Rails Web应用程序有许多方法,从调用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同时提交作业的使用。

Ruby拥有出色的承诺gem。 你的例子看起来像:

 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 gem并在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 

编辑: result错字,应该是result1 ,将echo更改为puts

你可以看看镇上的一个新选项: futoroscope gem 。 正如您在宣布博客文章中所看到的那样,它试图解决您面临的同一问题,同时进行API查询。 它似乎有很好的支持和良好的测试覆盖率。

假设您的问题是一个缓慢的外部API,解决方案可能是使用线程编程或异步编程。 默认情况下,在执行IO时,您的代码将被阻止。 这基本上意味着如果你有一个方法来执行HTTP请求来检索一些JSON,你的方法会告诉你的操作系统你将要睡觉,并且你不想在操作系统响应之前被唤醒那个要求。 由于这可能需要几秒钟,您的应用程序将无需等待。

此行为并非仅针对HTTP请求。 从文件或诸如网络摄像头之类的设备读取具有相同的含义。 软件这样做是为了防止在显然没有使用它时占用CPU。

所以你的问题是:我们真的必须等待一种方法才能完成另一种方法吗? 如果method_two的行为取决于method_two的结果, method_one yes。 但在你的情况下,它们似乎是没有共同依赖的个人工作单位。 因此有可能进行并发执行。

您可以通过使用包含您要运行的代码的块初始化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线程,当OS准备好输入时。

假设每个方法执行两个2毫秒减去等待响应,这意味着所有三个方法仅在6毫秒后运行 – 实际上是即时的。

如果我们假设响应需要500毫秒才能完成,这意味着您可以将总执行时间从2 + 500 + 2 + 500 + 2 + 500减少到2 + 2 + 2 + 500 – 换句话说就是从1506 ms到506毫秒。

感觉这些方法同时运行,但事实上它们只是同时睡觉。

但是,在您的情况下,您遇到了一个挑战,因为您的操作依赖于一组先前操作的完成。 换句话说,如果你有任务A,B,C,D,E和F,那么可以同时执行A,B,C,D和E,但是直到A,B,C,D和E才能执行F都完整了。

有不同的方法来解决这个问题。 让我们看一个简单的解决方案,它在主线程中创建一个困循环,定期检查返回值列表以确保某些条件已满。

 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. 

请记住,multithreading编程是一个棘手的主题,有许多陷阱。 使用MRI并不是那么糟糕,因为虽然MRI会很快地在被阻塞的线程之间切换,但是MRI不支持同时执行两个线程并且解决了很多并发问题。

如果你想进入multithreading编程,我推荐这本书: http : //www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601

它以Java为中心,但所解释的陷阱和概念是普遍存在的。

你应该看看Sidekiq 。

关于Sidekiq的RailsCasts一集。