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

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

Есть много задач, требующих запуска дочернего процесса. Ruby предоставляет широкий набор возможностей решения этой задачи. Все написанное ниже относится только к семейству UNIX-платформ (Linux, Mac OS X,...), т.к. управление дочерними процессами в ОС Windows построено по-другому. Рассмотрим возможности, предоставляемые Ruby что называется «из коробки» (out-of-the-box) без необходимости подключения внешних библиотек. Для начала определим метод, который будет исполняться в дочернем процессе (Полностью код примеров можно взять здесь):

def hello(source, expect_input)

  puts "[child] Hello from #{source}"

  if expect_input

    puts "[child] Standard input contains: \"#{$stdin.readline.chomp}\""

  else

    puts "[child] No stdin, or stdin is same as parent's"

  end

  $stderr.puts "[child] Hello, standard error"

end

Наш метод выводит сообщение в стандартный поток ввода-вывода, сообщение в поток вывода ошибок и опционально считывает сообщение из потока ввода-вывода и печатает его. Содержимое потоков ввода-вывода будет различаться в зависимости от способа запуска дочернего процесса.

Теперь определим пару констант:
require 'rbconfig'
THIS_FILE = File.expand_path(__FILE__)

 

RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])

Переменная THIS_FILE содержит полное имя файла, переменная RUBY содержит полный путь к исполняемому файлу интерпретатора Ruby. Полные пути необходимы для запуска скриптов с помощью shell. Чтобы выводимые программой сообщения сразу выдавались на экран установим:

$stdout.sync = true

И, наконец, заключим код, который не должен выполняться в дочернем процессе, между строчками:

if $PROGRAM_NAME == __FILE__
# ...
end

 

Метод 1: оператор ` (backtick)

Этот метод — самый простой запуска дочернего процесса. Он предоставляет наиболее богатые возможности по взаимодействию. С точки зрения backtick — это оператор языка Ruby, определённый в модуле Kernel и который может быть переопределён. В модуле Kernel он определён как метод, который выполняет в shell переданный ему аргумент.

  puts "1. Backtick operator"

  output = `#{RUBY} -r#{THIS_FILE} -e'hello("backticks", false)'`

  output.split("\n").each do |line|

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

  end

Результат запуска подпроцесса:

1. Backtick operator

[child] Hello, standard error

[parent] output: [child] Hello from backticks

[parent] output: [child] No stdin, or stdin is same as parent's

Оператор не возвращает управление в родительский процесс до тех пор, пока не завершится выполнение дочернего процесса. Результат выполнения дочернего процесса доступен через объект Process::Status в переменной $? (также известной как $CHILD_STATUS если загружена эта библиотека). Этот оператор можно записать с использованием оператора x%:

%x{echo `which cowsay`}

 

Метод 2: Kernel#system

Этот метод аналогичен предыдущему с тем отличием, что вместо потока ввода-вывода STDOUT возвращается логическое значение, указывающее на успешность/неуспешность завершения дочернего процесса. Если дочерний процесс завершился с кодом 0, то возвращается true, иначе — false.

  puts "2. Kernel#system"

  success = system(RUBY, "-r", THIS_FILE, "-e", 'hello("system()", false)')

  puts "[parent] success: #{success}"

Результат запуска подпроцесса:

2. Kernel#system

[child] Hello from system()

[child] No stdin, or stdin is same as parent's

[child] Hello, standard error

[parent] success: true

Как и в предыдущем случае управление в родительский процесс не возвращается до завершения дочернего работы дочернего, статус завершения дочернего процесса сохраняется в переменной $? и дочерний процесс наследует потоки ввода-вывод родительского. Метод system() позволяет динамически формировать командную строку и в этим он более гибок в формировании сложных команд, чем оператор backtick. Если нет необходимости разбирать результат работы дочернего процесса, то предпочтительнее использовать system(). Кроме того, метод system() может быть вызван еще несколькими способами. Подробнее можно узнать из описания метода Kernel#exec

Метод 3: Kernel#fork (известный как Process.fork)

Этот метод — доступ средствами Ruby к вызову метода fork() в *NIX-системах. В этом случае поток, вызывающий fork(), разделятся на два независимых конкурентно-выполняемых потока. Этот метод отличается от предыдущих тем, что позволяет исполнять в дочернем процессе код Ruby без явной команды запуска еще одного экземпляра интерпретатора и передачи ему файла с кодом. Код дочернего процесса помещается в блок кода Ruby:

  puts "3. Kernel#fork"

  pid = fork do

    hello("fork()", false)

  end

  Process.wait(pid)

  puts "[parent] pid: #{pid}"
Результат:
3. Kernel#fork

[child] Hello from fork()

[child] No stdin, or stdin is same as parent's

[child] Hello, standard error

[parent] pid: 19935

Дочерний процесс наследует потоки ввода-вывода родительского потока. Так как родительский и дочерний процессы выполняются независимо и конкурентно, то необходимо дождаться завершения выполнения дочернего процесса с помощью метода Process.wait(), предав ему идентификатор дочернего процесса, возвращенный вызовом fork().

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

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