Проблема с производительностью Ruby TCPServer

Я столкнулся с интересной проблемой с Ruby TCPServer, когда после подключения клиента он постоянно использует все больше и больше вычислительной мощности ЦП, пока не достигнет 100%, а затем вся система начинает зависать и не может обрабатывать входящие данные.

Класс обработки, в котором возникла проблема, разработан как TCP-клиент, который получает данные от встроенной системы, обрабатывает их, а затем возвращает обработанные данные для дальнейшего использования (либо другими аналогичными процессорами данных, либо для вывода пользователю).

В этом конкретном случае существует внешний фрагмент кода, который хотел бы получить эти обработанные данные, но не может получить к ним доступ из основного родительского кода (вещь, которой исходный класс процесса возвращает свои данные). Эта внешняя часть может быть подключена или не подключена в любой момент во время работы.

Чтобы решить эту проблему, я настроил поток с TCPServer, и класс обработки постоянно добавляет в очередь, а поток извлекает из очереди и отправляет ее клиенту.

Работает отлично, за исключением проблем с производительностью. Мне любопытно, происходит ли что-то странное в моем коде, или это просто природа этой методологии, и она никогда не будет достаточно производительной, чтобы работать.

Заранее спасибо за любые идеи/предложения по этой проблеме!

Вот мой код/настройка с некоторыми помощниками по тестированию:

process_data.rb

require 'socket'

class ProcessData

  def initialize
    super

    @queue = Queue.new
    @client_active = false

    Thread.new do
      # Waiting for connection
      @server = TCPServer.open('localhost', 5000)

      loop do

        Thread.start(@server.accept) do |client|
          puts 'Client connected'

          # Connection established
          @client_active = true

          begin
            # Continually attempt to send data to client
            loop do

              unless @queue.empty?
                # If data exists, send it to client
                begin
                  until @queue.empty?
                    client.puts(@queue.pop)
                  end
                rescue Errno::EPIPE => error
                  # Client disconnected
                  client.close
                end
              end
              sleep(1)
            end

          rescue IOError => error
            # Client disconnected
            @client_active = false
          end
        end # Thread.start(@server.accept)
      end # loop do
    end # Thread.new do

  end



  def read(data)
    # Data comes in from embedded system on this method

    # Do some processing
    processed_data = data.to_i + 5678 

    # Ready to send data to external client
    if @client_active
      @queue << processed_data
    end

    return processed_data
  end

end

test_embedded_system.rb (источник исходных данных)

require 'socket'

@data = '1234'*100000 # Simulate lots of data coming ing

embedded_system = TCPServer.open('localhost', 5555)

client_connection = embedded_system.accept
loop do
  client_connection.puts(@data)
  sleep(0.1)
end

parent.rb (это то, что создаст/вызовет класс ProcessData)

require_relative 'process_data'

processor = ProcessData.new
loop do
  begin
    s = TCPSocket.new('localhost', 5555)
    while data = s.gets
      processor.read(data)
    end
  rescue => e
    sleep(1)
  end
end

random_client.rb (запрашивает данные из ProcessData)

require 'socket'

loop do
  begin
    s = TCPSocket.new('localhost', 5000)
    while processed_data = s.gets
      puts processed_data
    end
  rescue => e
    sleep(1)
  end
end

Чтобы запустить тест в Linux, откройте 3 окна терминала:

Окно 1: ./test_embedded_system.rb

Окно 2: ./parent.rb

\Использование ЦП стабильно

Окно 3: ./random_client.rb

\Использование ЦП постоянно растет


person Tennesseej    schedule 08.11.2018    source источник
comment
то, что вы предоставили, собирается воспроизвести проблему с процессором? Можете ли вы поделиться своей версией рубина?   -  person Anthony    schedule 08.11.2018
comment
Версия Ruby: ruby ​​2.3.4p301 (30 марта 2017 г., редакция 58214) [x86_64-linux] Позвольте мне посмотреть, смогу ли я собрать хороший способ проверить это, я считаю, что это правильный код для воспроизведения проблемы, но это определенно не тривиальная установка   -  person Tennesseej    schedule 08.11.2018
comment
@ Энтони Я добавил тест к вопросу, дайте мне знать, если это поможет!   -  person Tennesseej    schedule 09.11.2018


Ответы (1)


В конце концов я выяснил, в чем проблема, и, к сожалению, своим примером я ввожу людей в заблуждение.

Оказывается, в моем примере не было той проблемы, которая была у меня, и основное отличие заключалось в том, что sleep(1) не было в моей версии process_data.rb.

Этот спящий режим на самом деле невероятно важен, потому что он находится внутри loop do, а без спящего режима поток не даст GVL и будет постоянно потреблять ресурсы ЦП.

По сути, это не было связано с TCP и больше связано с потоками и циклами.

Если вы наткнетесь на этот вопрос позже, вы можете поместить sleep(0) в свои циклы, если вы не хотите, чтобы он ждал, но вы хотите, чтобы он выдавал GVL.

Ознакомьтесь также с этими ответами для получения дополнительной информации: Бесконечный цикл Ruby вызывает 100% загрузка процессора

сон 0 имеет особое значение?

person Tennesseej    schedule 04.01.2019