非阻塞反引号

Non-blocking backticks

我想 运行 以非阻塞(异步)方式从 Ruby 执行多个耗时的 shell 命令。

我想将选项传递给命令,在 Ruby 中接收输出,并且(理想情况下)处理错误。

下面的脚本执行自然需要15秒:

test.rb

3.times do |i|
  puts `sleep 5; echo #{i} | tail -n 1` # some time-consuming complex command
end
$ /usr/bin/time ruby test.rb
0
1
2
       15.29 real         0.13 user         0.09 sys

有了Thread,显然可以并行执行,不出所料只需5秒:

threads = []

3.times do |i|
  threads << Thread.new {
    puts `sleep 5; echo #{i} | tail -n 1`
  }
end

threads.each {|t| t.join() }
$ /usr/bin/time ruby test.rb
2
0
1
        5.17 real         0.12 user         0.06 sys

但这是最​​好的方法吗?还有其他办法吗?

我也使用 Open3.popen2 编写过,但这似乎需要 15 秒才能执行,如第一个示例(除非包裹在线程中):

require 'open3'

3.times do |i|
  Open3.popen2("sleep 5; echo #{i} | tail -n 1") do |stdin, stdout|
    puts stdout.read()
  end
end

The documentation描述了“块形式”和“非块形式”,但是这个“块”是指匿名函数,与并发无关,对吗?

Open3 class 只能阻塞执行吗?

您的代码的问题是 stdout.read 是一个阻塞调用。

您可以推迟阅读,直到命令完成。

首先,创建命令:

commands = Array.new(3) { |i| Open3.popen2("sleep 5; echo hello from #{i}") }

然后,等待每个命令完成:

commands.each { |stdin, stdout, wait_thr| wait_thr.join }

最后,收集输出并关闭 IO 流:

commands.each do |stdin, stdout, wait_thr|
  puts stdout.read
  stdin.close
  stdout.close
end

输出:(5 秒后)

hello from 0
hello from 1
hello from 2