我有一个很大的循环,我试图在线程中运行对Open3.capture3
的调用,而不是线性运行。每个线程应独立运行,并且在访问数据方面没有死锁。
问题是,线程版本的运行速度慢得多,并且占用了我的CPU。
这是线性程序的示例:
require 'open3'
def read(i)
text, _, _ = Open3.capture3("echo Hello #{i}")
text.strip
end
(1..400).each do |i|
puts read(i)
end
这是线程版本:
require 'open3'
require 'thread'
def read(i)
text, _, _ = Open3.capture3("echo Hello #{i}")
text.strip
end
threads = []
(1..400).each do |i|
threads << Thread.new do
puts read(i)
end
end
threads.each(&:join)
时间比较:
$ time ruby linear.rb
ruby linear.rb 0.36s user 0.12s system 110% cpu 0.433 total
------------------------------------------------------------
$ time ruby threaded.rb
ruby threaded.rb 1.05s user 0.64s system 129% cpu 1.307 total
答案 0 :(得分:3)
每个线程应该独立运行,并且在访问数据方面没有死锁。
您确定吗?
threads << Thread.new do
puts read(i)
end
您的线程正在共享标准输出。如果查看输出,您将看到没有任何交错的文本输出,因为Ruby自动确保在stdout上互斥,因此您的线程通过一系列无用的构造/解构/切换有效地串行运行浪费时间。
Ruby中的线程仅在调用某些Rubyless上下文*时才对并行性有效。这样,VM知道它可以安全地并行运行,而线程之间不会相互干扰。看一下如果我们仅捕获线程中的shell输出会发生什么:
threads = Array.new(400) { |i| Thread.new { `echo Hello #{i}` } }
threads.each(&:join)
# time: 0m0.098s
连续播放
output = Array.new(400) { |i| `echo Hello #{i}` }
# time: 0m0.794s
*实际上,这取决于几个因素。一些VM(JRuby)使用本机线程,并且更易于并行化。某些Ruby表达式比其他Ruby表达式更可并行化(取决于它们与GVL的交互方式)。确保并行性的最简单方法是运行单个外部命令,例如子进程或syscall,这些命令通常不使用GVL。