我有一个控制器,我试图从远程源获取XML文件。
类似的东西:
@artist = Nokogiri.XML(open(url).read)
但是,我想一次执行多个这些以获取不同的数据。我可以用某种方式使用线程吗?
单独执行一次需要400毫秒。因此,当它们连续三次执行时,响应最多可达1s +。
答案 0 :(得分:5)
是的,你可以使用线程:
named_urls = {
artist: 'http://foo.com/bar',
song: 'http://foo.com/jim',
# etc.
}
@named_xmls = {}
one_at_a_time = Mutex.new
named_urls.map do |name,url|
Thread.new do
doc = Nokogiri.XML(open(url).read)
one_at_a_time.synchronize{ @named_xmls[name] = doc }
end
end.each(&:join)
# At this point @named_xmls will be populated will all Nokogiri documents
我不确定在共享哈希中写入不同的密钥是否需要Mutex,但是为了安全起见并没有坏处。
答案 1 :(得分:4)
对于大量网址,您无法打开大量线程,因为您将使连接带宽饱和,并且您将开始出现连接错误。对于我的特定电缆调制解调器和特定服务器,我发现16个线程是一个很好的值。
我使用 Mathematica 来控制和改变我的ruby web报废程序的线程数,并监控其在不同线程数下的性能。这是结果:
我没有直接使用Thread.new
,而是编写了一个包装函数,只有在线程总数小于配置的最大值时才会打开一个新线程:
def maybe_new_thread
File.open('max_threads.cfg', 'r') { |file| @MAX_THREADS = file.gets.to_i }
if Thread.list.size < @MAX_THREADS
Thread.new { yield }
else
yield
end
end
请注意,所需线程的最大数量只是存储在名为max_threads.cfg
的文件中的数字,每次调用该函数时都会读取该文件。这允许您在程序运行时更改此变量的值。
该计划的一般结构如下:
named_urls = [ 'http://foo.com/bar', (... hundreds of urls ... ),'http://foo.com/jim']
named_urls.each do |url|
maybe_new_thread do
doc = Nokogiri.HTML(open(url))
process_and_insert_in_database(doc)
end
end
请注意,每个线程都将其结果存储在数据库中,因此我不需要使用Mutex类来协调线程之间的任何内容。
当我在数据库中插入时,我会在每个结果插入时包含一个精确时间的列。这是至关重要的,以便您可以计算您获得的性能。确保您使用毫秒支持定义此列(我使用MariaDB 5.3)。
这是我在 Mathematica 中使用的代码,用于控制最大线程数并实时绘制图形:
named_urls = {
'http://foo.com/bar', (... hundreds of urls ... ),'http://foo.com/jim',
}
named_urls.each do |url|
maybe_new_thread do
doc = Nokogiri.HTML(open(url))
process_and_insert_in_database(doc)
end
end
setNumberOfThreads[n_] := Module[{},
Put[n, "max_threads.cfg"];
SQLExecute[conn,"DELETE FROM results"]]
operationsPerSecond := SQLExecute[conn,
"SELECT
(SELECT COUNT(*) FROM results)/
(SELECT TIME_TO_SEC(TIMEDIFF((SELECT fin FROM results ORDER BY finishTime DESC LIMIT 1),
(SELECT fin FROM results ORDER BY finishTime LIMIT 1))))"][[1, 1]];
cops = {};
RunScheduledTask[AppendTo[cops, operationsPerSecond], 2];
Dynamic[ListLinePlot[cops]]
当它正在运行时,一旦您发现性能稳定,您可以使用setNumberOfThreads[]
更改线程数,并查看性能效果。
最后一条评论。我没有直接使用open-uri的open方法,而是使用这个包装器,因此遇到错误,它会自动重试:
def reliable_open(uri)
max_retry = 10
try_counter = 1
while try_counter < max_retry
begin
result = open(uri)
return result
rescue
puts "Error when trying to open #{uri}"
try_counter += 1
sleep try_counter * 10
end
end
raise "Imposible to open after #{max_retry} retries"
end