Обсуждение подпроцесса с использованием Ruby с вводом-выводом и многопоточностью.
-
23-09-2019 - |
Вопрос
Я пытаюсь использовать IO.popen, чтобы помещать (с помощью метода .puts) и получать (с помощью метода .gets) сообщения из процесса в его подпроцесс.
Я не очень экспериментатор и у меня есть вопрос.Имея следующий код, у меня возникает ошибка, поскольку невозможно писать в закрытом потоке.
class Interface
def initialize(path)
@sub_process = IO.popen(path, 'w+')
end
def start!
if ok?
@sub_process.puts 'Hello', 'my name is ...'
# and more...
end
end
protected
def ok?
is_ready?(@sub_process) && is_cool?(@sub_process)
end
def is_ready?(sub_process)
reply = process_command(sub_process, 'are u ready?')
reply.chomp.match(/yes_i_am_ready$/)
end
def is_cool?(sub_process)
reply = process_command(sub_process, 'are u cool?')
reply.chomp.match(/yes_i_am_cool$/)
end
def process_command(sub_process, command)
rdr = Thread.new { sub_process.read } # alternative: io.readlines
sub_process.puts "#{command}"
sub_process.close_write
rdr.value # joins and fetches the result
end
end
a = Interface.new("./program")
a.start!
(...) в `писать':не открыт для записи (IOError)
Как мы видим, эта ошибка возникает во время is_cool?тест (как описано в: http://ruby-doc.org/core/classes/IO.html#M002289).
Но если я попытаюсь прокомментировать в методеprocess_command строку:
# sub_process.close_write
скрипт похоже уснул...бесконечно :с
Я считаю, что невозможно снова открыть закрытый поток.И я не могу создать другой экземпляр IO.popen моей программы «./program», потому что его необходимо инициализировать с помощью какой-либо команды (например, «ты готов?» и «ты крут?») в начале, прежде чем Я использую его (отправляя и получая сообщения, как простое обсуждение).
Как я могу внести изменения в текущий код, чтобы решить эту проблему?
Редактировать:другими словами, я хотел бы установить такую связь (по заданному протоколу):
Parent message: Child answer:
-------------- ------------
'are u ready?' 'yes_i_am_ready'
'are u cool?' 'yes_i_am_cool'
'Hello' 'foo'
'my name is ...' 'bar'
Большое спасибо за любую помощь.
Решение
Возможно, это поможет иметь рабочий пример.Вот один из них, протестированный и работающий в MRI 1.8.7 в Linux.
bar.rb
#!/usr/bin/ruby1.8
begin
loop do
puts "You said: #{gets}"
$stdout.flush
end
rescue Errno::EPIPE
end
foo.rb
#!/usr/bin/ruby1.8
class Parent
def initialize
@pipe = IO.popen(CHILD_COMMAND, 'w+')
end
def talk(message)
@pipe.puts(message)
response = @pipe.gets
if response.nil?
$stderr.puts "Failed: #{CHILD_COMMAND}"
exit(1)
end
response.chomp
end
private
CHILD_COMMAND = './bar.rb'
end
parent = Parent.new
puts parent.talk('blah blah blah')
puts parent.talk('foo bar baz')
вывод foo.rb
You said: blah blah blah
You said: foo bar baz
Другие советы
Закрытый IO
больше использовать нельзя.Не следует закрывать IO
если вы собираетесь продолжать его использовать.
Если вы удалите IO#close_write
проблема с вашим кодом в следующей строке все еще остается.
rdr = Thread.new { sub_process.read }
IO#read
читаем до EOF.Таким образом, пока поток не будет закрыт, он никогда не завершится.Ты упомянул IO#readline
в вашем коде это будет лучшей альтернативой.С использованием IO#readline
ваша программа зависнет только в том случае, если процесс popend никогда не отправит новую строку.
Другая проблема с popen заключается в следующем. IO#popen
создать новый процесс.Процессы могут быть остановлены вами, другими пользователями, нехваткой памяти и т. д.Не ожидайте, что ваш процесс будет работать постоянно.Если процесс убит IO#readline
бросит EOFError
, IO#read
вернется немедленно.Вы можете определить причину прекращения с помощью следующего кода.
Process::wait(io.pid)
status= $?
status.class # => Process::Status
status.signaled? # killed by signal?
status.stopsig # the signal which killed it
status.exited # terminated normal
status.exitstatus # the return value
status.ki
Поможет ли использование этой формы Thread.new
?
rdr = Thread.new(sub_process) {|x| x.readlines }