Как избежать последовательного создания объекта Ruby Thread в этом коде?

До сих пор я никогда не использовал Thread, но думаю, что в этом случае я должен положиться на него. Я хотел бы обрабатывать stdout и stderr командной строки cURL отдельно, потому что я хочу обменять возврат каретки в индикаторе выполнения (который записывается в stderr) на новые строки:

require "open3"
cmd="curl -b cookie.txt #{url} -L -o -"
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|

  pid = wait_thr.pid 

  # I have to process stdout and stderr at the same time but
#asyncronously, because stdout gives much more data then the stderr
#stream. I instantiate a Thread object for reading the stderr, otherwise 
#"getc" would block the stdout processing loop.

  c=nil
  line=""
  stdout.each_char do |b| 
       STDOUT.print b

       if c==nil then
         c=""
         thr = Thread.new { 
         c=stderr.getc 
         if c=="\r" || c=="\n" then 
            STDERR.puts line 
            line=""
         else
          line<<c
         end
         c=nil
        }
  end

  #if stderr still holds some output then I process it:
  line=""
  stderr.each_char do |c|

         if c=="\r" || c=="\n" then 
            STDERR.puts line 
            line=""
         else
          line<<c
         end
  end

  exit_status = wait_thr.value.exitstatus 
  STDERR.puts exit_status

end #popen3

Мой вопрос: как я могу избежать создания нового экземпляра Thread в каждом цикле цикла при обработке stdout (stdout.each_char)? Я думаю, что это отнимает много времени, я хотел бы создать экземпляр один раз, а затем использовать его методы, такие как остановка и запуск и т. д.


person Konstantin    schedule 06.07.2015    source источник
comment
почему обе логические части читают stderr.getc (вторая использует each_char)?   -  person Arie Xiao    schedule 06.07.2015
comment
Потому что я обнаружил, что поток stderr все еще содержит данные, когда stdout уже пуст.   -  person Konstantin    schedule 06.07.2015
comment
Вам было бы намного лучше использовать select для управления чтением из нескольких объектов ввода-вывода.   -  person Frederick Cheung    schedule 06.07.2015


Ответы (1)


Как правило, вы можете обработать один из stdout, stderr в основном потоке и создать экземпляр другого потока для обработки другого. Это обычная практика одновременной обработки нескольких источников.

Вам нужно обратить внимание на совместное использование памяти в многопоточном контексте. В вашем случае line, stderr совместно используются и изменяются в нескольких потоках без синхронизации, что приводит к непредсказуемому поведению.

В большинстве случаев Ruby обрабатывает окончания строк за вас. Я не совсем понимаю необходимость обработки \r, \n вручную здесь.

require "open3"
cmd="curl -b cookie.txt #{url} -L -o -"
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
  pid = wait_thr.pid

  stdout_thread = Thread.new do
    # process stdout in another thread
    # you can replace this with the logic you want, 
    # if the following behavior isn't what you want
    stdout.each_line do |line|
      puts line
    end
  end

  # process stderr in the main thread
  stderr.each_line do |line|
    STDERR.puts line
  end

  # wait the stdout processing to be finished.
  stdout_thread.join
end
person Arie Xiao    schedule 06.07.2015
comment
Мне нужно заменить возврат каретки \\r на новую строку \\n, потому что этот код выполняется в программе CGI, а мой файл журнала Apache содержит каждую строку stderr, разделенную на отдельные символы, в первом случае с добавлением другой информации (IP, время, referal), много строк, а размер лог-файла растет очень быстро. Однако во втором случае, когда возврат каретки заменяется символом новой строки, он будет содержать отдельные строки. - person Konstantin; 07.07.2015