在Ruby中运行多线程Open3调用

时间:2019-01-10 15:51:56

标签: ruby multithreading popen3

我有一个很大的循环,我试图在线程中运行对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

1 个答案:

答案 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。