如何防止我的 UDP keepalive 使用 100% CPU?

How can I prevent my UDP keepalive from using 100% CPU?

我有一个 Runnable 监视数据以发送 UDP 以及每 10 秒发送一次保持活动状态。该过程占用 100% CPU。我尝试将线程设置为低优先级,但似乎没有任何区别。

private Runnable keepAliveRunnable = new Runnable() {

    long nextSend = 0;
    byte[] sendData;

    @Override
    public void run() {
        if(DEBUG)
            System.out.println("Starting keepAlive.");
        while (socket != null) {
            synchronized (socketLock) {
                try {
                    sendData = sendQueue.poll();
                    if (sendData != null) {
                        socket.send(new DatagramPacket(sendData, sendData.length,
                                InetAddress.getByName(Main.ipAddress), 10024));
                    } else if (nextSend < System.currentTimeMillis()) {
                        if(DEBUG && nextSend < System.currentTimeMillis())
                            System.out.println("Update keepAlive.");
                        // Send /xremote
                        socket.send(new DatagramPacket(("/xremote").getBytes(),
                                ("/xremote").getBytes().length,
                                InetAddress.getByName(Main.ipAddress), 10024));
                        nextSend = System.currentTimeMillis() + keepAliveTimeout;
                        // Send /info
                        socket.send(new DatagramPacket(("/info").getBytes(),
                                ("/info").getBytes().length,
                                InetAddress.getByName(Main.ipAddress), 10024));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    if(!e.getMessage().contains("Socket closed")) {
                        e.printStackTrace();
                    }
                }
            }
        }
        System.out.println("keepAliveRunnable ended.");
    }
};

您应该在 while 循环的底部添加一个 Thread.sleep(),以减慢您的循环。实际上,您正在 busy-waiting 并在等待 nextSend 时间到达时搅动 CPU。 Thread.sleep() 实际上会暂停线程,允许其他线程和进程在这个线程休眠时使用 CPU。

如果您的目标是实际每 10 秒执行一次工作,那么在循环迭代之间休眠 10 秒(100 毫秒)应该是一个很好的休眠时间。

经常有更高级的工作调度技术,例如 ScheduledExecutorService,您也可以考虑使用。但对于小型应用程序,您使用的模式很好,只是避免忙等待。

使 sendQueue 成为 LinkedBlockingQueue,并使用轮询超时。

你是 busy waiting,这实际上迫使你的应用程序一遍又一遍地保持 运行 相同的逻辑,而不是将 CPU 返回给系统。

不要指望您自己实施检查时间,这是不可靠的,并且可能会导致您所看到的结果。相反,使用 blockingQueue.poll(10, TimeUnit.SECONDS),它会自动处理将 CPU 返回给系统。

我对您的代码进行了一些其他更改;我把重复的数据包构造代码放在一个单独的方法中,只有在实际使用套接字时才包装套接字的同步。当您让队列为您完成工作时,请注意它有多干净。

while(socket != null) {
  try {
    sendData = sendQueue.poll(10, TimeUnit.SECONDS);
    if (sendData != null) {
      sendPacket(sendData);
    } else {
      sendPacket("/xremote".getBytes());
      sendPacket("/info".getBytes());
    }
  } catch (IOException e) {
    e.printStackTrace();
    if (!e.getMessage().contains("Socket closed")) {
      e.printStackTrace();
    }
  }
}

这里是 sendPacket:

private static void sendPacket(byte[] data) throws UnknownHostException, IOException {
  // Note, you probably only have to do this once, rather than looking it up every time.
  InetAddress address = InetAddress.getByName(Main.ipAddress);
  DatagramPacket p = new DatagramPacket(data, data.length, address, 10024);
  synchronized(socketLock) {
    socket.send(p);
  }
}

我认为与其轮询您的发送队列,不如使用信号量信号并等待更好。

当有数据包插入sendqueue时,调用semaphore信号。 使用信号量等待而不是调用 sendqueue.poll()。

我假设您有单独的线程用于从发送队列推送弹出数据。

这是标准的消费者生产者问题。 https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem

在深入研究我的代码后,我意识到随着时间的推移,我已经将发送数据的进程数量减少到 1(duh),所以我真的不需要 runnable,因为我可以直接发送数据。我还设置了一个单独的 runnable 并使用了 ScheduledExecutor。我想我会把它放在这里让其他人看到。 Durron597的代码更漂亮一些,但由于我现在只发送两个包,所以我决定将代码放在一起。

// In main
pingXAir();


private void pingXAir() {
    System.out.println("Start keepAlive");
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.scheduleAtFixedRate(keepAliveRunnable, 0, 5, TimeUnit.SECONDS);
}

private Runnable keepAliveRunnable = new Runnable() {

    @Override
    public void run() {
        synchronized (socketLock) {
            try {
                if (DEBUG)
                    System.out.println("Update keepAlive.");

                // Send /xremote
                socket.send(new DatagramPacket(("/xremote").getBytes(),
                        ("/xremote").getBytes().length,
                        InetAddress.getByName(Main.ipAddress), 10024));

                // Send /info
                socket.send(new DatagramPacket(("/info").getBytes(),
                        ("/info").getBytes().length,
                        InetAddress.getByName(Main.ipAddress), 10024));

            } catch (IOException e) {
                e.printStackTrace();
                if (!e.getMessage().contains("Socket closed")) {
                    e.printStackTrace();
                }
            }
        }
    }
};