Запуск дочерних процессов в Ruby. Часть 2

Подготовлено по материалам второй части статьи «A dozen (or so) ways to start sub-processes in Ruby: Part 2» Автор: Avdi.

Одним из недостатков ранее описанных методов запуска подпроцессов было то, что было нельзя установить полноценный обмен информацией между родительским и дочерним процессами. Рассмотрим варианты, предоставляющие возможность организации такого обмена. Полный исходный код примеров доступен здесь.

 Метод 4: использование пайпа (pipe)

 Как Вы знаете, метод Kernel#open позволяет открывать файлы (а при использовании библиотеки open-uri и HTTP-сокеты). Более того, этот метод позволяет открывать подпроцессы и взаимодействовать с ними как с файлами:

puts "4a. Kernel#open with |"

cmd = %Q<|#{RUBY} -r#{THIS_FILE} -e 'hello("open(|)", true)'>

open(cmd, 'w+') do |subprocess|

  subprocess.write("hello from parent")

  subprocess.close_write

  subprocess.read.split("\n").each do |l|

    puts "[parent] output: #{l}"

  end

  puts

end

puts "---"

Передавая символ pipe ('|') мы говорим, что надо запустить процесс, а не открыть файл. Как результат выполнения команды запускается отдельный Ruby-процесс и в нём 'дергается' функция hello. Здесь функция open возвращает IO-объект, который можно использовать для обмена информацией с дочерним процессом. Всё, что записывается в этот объект, поступит в поток stdin дочернего процесса, а всё, что подпроцесс запишет в поток stdout, может быть прочитано родительским процессом. В примере выше производится запись данных в поток дочернего процесса, затем происходит считывание данных из дочернего процесса. Особое внимание следует обратить на:

close_write

Эту команду нужно выполнить чтобы данные были вытолкнуты из буферов ввода-вывода дочернего процесса и переданы дочернему процессу. Вызов приводит к сбросу данных из буферов ввода-вывода дочернего процесса и посылке команды EOF в stdin дочернего процесса. После завершения дочернего процесса данные из его выходных буферов будут считаны в родительский процесс и родительский процесс получит управление. Следует обратить внимание на параметр «w+», который заставляет открыть подпроцесс с возможностью записи. Результат:

4a. Kernel#open with |

[child] Hello, standard error

[parent] output: [child] Hello from open(|)

[parent] output: [child] Standard input contains: "hello from parent"


Альтернативным вариантом создания IO-объекта является вызов IO#popen:
puts "4b. IO.popen"

cmd = %Q<#{RUBY} -r#{THIS_FILE} -e 'hello("popen", true)'>

IO.popen(cmd, 'w+') do |subprocess|

  subprocess.write("hello from parent")

  subprocess.close_write

  subprocess.read.split("\n").each do |l|

    puts "[parent] output: #{l}"

  end

  puts

end

puts "---"

Использование аналогично использованию метода Kernel#open.

Метод 5: запуск пайпа (pipe) в отдельном потоке

Этот метода является вариацией предыдущего. Если методу Kernel#open передать символ команду «|-» в качестве первого аргумента, то он запустит подпроцесс Ruby:

puts "5a. Kernel#open with |-"

open("|-", "w+") do |subprocess|

  if subprocess.nil?             # child

    hello("open(|-)", true)

    exit

  else                        # parent

    subprocess.write("hello from parent")

    subprocess.close_write

    subprocess.read.split("\n").each do |l|

      puts "[parent] output: #{l}"

    end

    puts

  end

end

puts "---"

Родительский и дочерний процессы будут выполнять один и тот же Ruby-блок кода, но в дочерний процесс в блок будет передано значение nil, а в родительский — IO-объект. Как и в предыдущем случае этот объект связан с stdin и stdout дочернего процесса. Результат:

5a. Kernel#open with |-

[child] Hello, standard error

[parent] output: [child] Hello from open(|-)

[parent] output: [child] Standard input contains: "hello from parent"


---

Аналогичного результата можно добиться используя метод IO#popen:
puts "5b. IO.popen with -"

IO.popen("-", "w+") do |subprocess|

  if subprocess.nil?             # child

    hello("popen(-)", true)

    exit

  else                        # parent

    subprocess.write("hello from parent")

    subprocess.close_write

    subprocess.read.split("\n").each do |l|

      puts "[parent] output: #{l}"

    end

    puts

  end

end

puts "---"

Приложения и предостережения

 Рассмотренные выше способы подходят для построения так называемых «filter-style» приложений, когда надо передать какие-то данные дочернему приложению и получить от него результаты. Из-за возможности появления deadlock'ов такая схема взаимодействия не подходит для потоков с интенсивным обменом данными. Кроме того, методы open/popen не позволяют получить доступ к стандартному потоку вывода ошибок дочернего процесса: все данные, выводимые в этот поток дочерним приложением, направляются в поток ошибок родительского.

Подготовлено КОМТЕТ по материалам devver.net.

Вам также может помочь