简体   繁体   English

Ruby TCPServer性能问题

[英]Ruby TCPServer performance issue

I am encountering an interesting issue with Ruby TCPServer, where once a client connects, it continually uses more and more CPU processing power until it hits 100% and then the entire system starts to bog down and can't process incoming data. 我在Ruby TCPServer上遇到了一个有趣的问题,即客户端连接后,它将持续使用越来越多的CPU处理能力,直到达到100%,然后整个系统开始陷入瘫痪,无法处理传入的数据。

The processing class that is having an issue is designed to be a TCP Client that receives data from an embedded system, processes it, then returns the processed data to be further used (either by other similar data processors, or output to a user). 存在问题的处理类被设计为TCP客户端,该TCP客户端从嵌入式系统接收数据,对其进行处理,然后返回处理后的数据以供进一步使用(由其他类似的数据处理器或输出给用户)。

In this particular case, there is an external piece of code that would like this processed data, but cannot access it from the main parent code (the thing that the original process class is returning it's data to). 在这种特殊情况下,有一段外部代码想要处理的数据,但是无法从主父代码(原始流程类将其数据返回给它的东西)中访问它。 This external piece may or may not be connected at any point while it is running. 该外部部件在运行时可能随时连接,也可能未连接。

To solve this, I set up a Thread with a TCPServer, and the processing class continually adds to a queue, and the Thread pulls from the queue and sends it to the client. 为了解决这个问题,我用TCPServer设置了一个线程,并且处理类不断添加到队列中,并且线程从队列中拉出并将其发送给客户端。

It works great, except for the performance issues. 除性能问题外,它的效果都很好。 I am curious if I have something funky going on in my code, or if it's just the nature of this methodology and it will never be performant enough to work. 我很好奇我的代码中是否有一些时髦的东西,或者这仅仅是这种方法的本质,并且永远无法发挥足够的性能。

Thanks in advance for any insight/suggestions with this problem! 在此先感谢您对这个问题的任何见解/建议!

Here is my code/setup, with some test helpers: 这是我的代码/设置,以及一些测试助手:

process_data.rb 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 (source of the original data) 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 (this is what will create/call the ProcessData class) 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 (wants data from ProcessData) 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

To run the test in linux, open 3 terminal windows: 要在linux中运行测试,请打开3个终端窗口:

Window 1: ./test_embedded_system.rb 窗口1:./ test_embedded_system.rb

Window 2: ./parent.rb 窗口2:./ parent.rb

\\CPU usage is stable \\ CPU使用率稳定

Window 3: ./random_client.rb 窗口3:./ random_client.rb

\\CPU usage continually grows \\ CPU使用率持续增长

I ended up figuring out what the issue was, and unfortunately I lead folks astray with my example. 我最终弄清了问题所在,不幸的是,我让人们误以为是我的榜样。

It turns out my example didn't quite have the issue I was having, and the main difference was the sleep(1) was not in my version of process_data.rb. 事实证明,我的示例并没有完全解决我遇到的问题,主要的区别是sleep(1)不在我的process_data.rb版本中。

That sleep is actually incredibly important, because it is inside of a loop do , and without the sleep, the Thread won't yield the GVL, and will continually eat up CPU resources. 睡眠实际上是非常重要的,因为它是在loop do ,没有睡眠,线程将不会产生GVL,并且会不断消耗CPU资源。

Essentially, it was unrelated to TCP stuff, and more related to Threads and loops. 本质上,它与TCP无关,而更多与线程和循环相关。

If you stumble on this question later on, you can put a sleep(0) in your loops if you don't want it to wait, but you want it to yield the GVL. 如果您稍后在此问题上遇到问题,则可以在不需要的情况下将sleep(0)放入循环中,但希望它产生GVL。

Check out these answers as well for more info: Ruby infinite loop causes 100% cpu load 还要查看以下答案以获取更多信息: Ruby无限循环导致100%cpu负载

sleep 0 has special meaning? sleep 0有特殊含义吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM