1

我有一个大循环,我试图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
4

1 回答 1

3

每个线程都应该独立运行,并且在访问数据方面没有死锁。

您确定吗?

threads << Thread.new do
  puts read(i)
end

您的线程正在共享标准输出。如果你查看你的输出,你会发现你没有得到任何交错的文本输出,因为 Ruby 自动确保标准输出上的互斥,所以你的线程有效地串行运行,带有一堆无用的构造/解构/切换浪费时间。

Ruby 中的线程仅在调用某些无 Ruby 上下文时才对并行性有效*。这样,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 表达式比其他表达式更可并行化(取决于它们如何与 GVL 交互)。确保并行性的最简单方法是运行单个外部命令,例如子进程或系统调用,这些通常是无 GVL 的。

于 2019-01-10T17:25:07.250 回答