Quiero ejecutar varios comandos de shell que consumen mucho tiempo desde Ruby de forma no bloqueante (asincrónica).
Quiero pasar opciones a los comandos, recibir resultados en Ruby e (idealmente) manejar errores.
Naturalmente, el siguiente script tardará 15 segundos en ejecutarse:
prueba.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 sysCon Thread , aparentemente se puede ejecutar en paralelo y toma solo 5 segundos, como se esperaba:
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 sysPero, ¿es este el mejor enfoque? ¿Hay alguna otra manera?
También he escrito usando Open3.popen2 , pero esto parece tardar 15 segundos en ejecutarse como en el primer ejemplo (a menos que esté envuelto en un hilo):
require 'open3' 3.times do |i| Open3.popen2("sleep 5; echo #{i} | tail -n 1") do |stdin, stdout| puts stdout.read() end endLa documentación describe "forma de bloque" y "forma de no bloque", pero este "bloque" se refiere a funciones anónimas y no tiene nada que ver con la concurrencia, ¿correcto?
¿La clase Open3 solo es capaz de bloquear la ejecución?
El problema con su código es que stdout.read es una llamada de bloqueo.
Puede diferir la lectura hasta que finalice el comando.
Al principio, crea los comandos:
commands = Array.new(3) { |i| Open3.popen2("sleep 5; echo hello from #{i}") }Luego, espere a que termine cada comando:
commands.each { |stdin, stdout, wait_thr| wait_thr.join }Finalmente, recopile la salida y cierre los flujos de IO:
commands.each do |stdin, stdout, wait_thr| puts stdout.read stdin.close stdout.close endSalida: (después de 5 segundos)
hello from 0 hello from 1 hello from 2