Запуск дочерних процессов в Ruby. Часть 2
Одним из недостатков ранее описанных методов запуска подпроцессов было то, что было нельзя установить полноценный обмен информацией между родительским и дочерним процессами. Рассмотрим варианты, предоставляющие возможность организации такого обмена. Полный исходный код примеров доступен здесь.
Метод 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.