如何防止我的 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();
}
}
}
}
};
我有一个 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();
}
}
}
}
};