【问题标题】:Java - Most optimal way to poll for timeoutJava - 轮询超时的最佳方式
【发布时间】:2012-12-22 05:24:09
【问题描述】:

我有这样的代码:

while(isResponseArrived)
   Thread.yield();

但我真正想做的是这样的:

long startTime = System.currentTimeInMilliseconds();

while(isResponseArrived)
{
   if(isTimeoutReached(startTime))
       throw new TimeOutExcepton(); 
   Thread.yield();
}

我还不确定是否抛出异常(这对这个问题并不重要),但我想知道的是如何使其尽可能高效,所以我不会继续处理器。换句话说,我怎样才能使 isTimeoutReached(long startTime) 尽可能提高性能。

我测试过:

for(int x=0; x<99999999; x++)
    System.nanoTime();

对比

for(int x=0; x<99999999; x++)
    System.currentTimeInMilliseconds();

而且差别很小,在完成时间方面不到 10%

我还考虑使用 Thread.sleep(),但我真的希望在有更新并且处理器正在等待时尽快通知用户。 Thread.yield() 不会让处理器搅动,它只是一个 NOP,给予其他任何处理器优先级,直到它可以继续。

无论如何,在不限制 CPU 的情况下测试超时的最佳方法是什么?这是正确的方法吗?

【问题讨论】:

  • 如何设置isResponseArrived=true?如果它是由网络响应设置的,则最佳方式是读取超时的输入通道。
  • 我不能,因为一旦联网通道打开,它就会一直保持打开状态,直到我关闭它。它不像 html 调用那样立即打开和关闭网络,在这种情况下,连接始终是打开的,更像是 ssh 连接。但我想做的是限制任何一个命令可以花费的时间。

标签: java


【解决方案1】:

根据我的经验,超时是任意选择的,例如不是时间关键的。如果我选择 1000 毫秒的超时并且它需要 1001 毫秒而不是影响应该是微不足道的。对于实现超时,我建议使实现尽可能简单。

您可以使用 ScheduledExecutorService 实现超时,例如

final ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();

public void addTimeoutForTask(final Future future, int timeOutMS) {
    ses.schedule(new Runnable() {
        @Override
        public void run() {
            future.cancel(true);
        }
    }, timeOutMS, TimeUnit.MILLISECONDS);
}

如果您正在执行一些非阻塞操作并且您希望它超时,您可以这样做。

interface TimedPoller {
    public void poll();

    /**
     * @return is it now closed.
     */
    public boolean checkTimeout(long nowNS);
}

private final Set<TimedPoller> timedPollers = new LinkedHashSet<>();
private volatile TimedPoller[] timedPollersArray = {};

public void add(TimedPoller timedPoller) {
    synchronized (timedPollers) {
        long nowNS = System.nanoTime();
        if (!timedPoller.checkTimeout(nowNS) && timedPollers.add(timedPoller))
            timedPollersArray = timedPollers.toArray(new TimedPoller[timedPollers.size());
    }
}

public void remove(TimedPoller timedPoller) {
    synchronized (timedPollers) {
        if (timedPollers.remove(timedPoller))
            timedPollersArray = timedPollers.toArray(new TimedPoller[timedPollers.size());
    }
}

private volatile boolean running = true;

public void run() {
    while (running) {
        // check the timeout for every 1000 polls.
        for (int i = 0; i < 1000; i += timedPollersArray.length) {
            TimedPoller[] pollers = timedPollersArray;
            for (TimedPoller poller : pollers) {
                poller.poll();
            }
        }
        long nowNS = System.nanoTime();
        TimedPoller[] pollers = timedPollersArray;
        for (TimedPoller poller : pollers) {
            if (poller.checkTimeout(nowNS))
                remove(poller);
        }
    }
}

要么放弃 CPU,要么不放弃。如果你放弃 CPU,其他线程可以运行,但你会得到一个延迟,然后才能再次运行。或者你没有放弃 CPU 来提高你的响应时间,但是另一个线程无法运行。

看来您希望能够让其他东西运行,而无需放弃 CPU。这并非微不足道,但如果做得正确,可以为您带来两者的一些好处(如果做得不好,则两者都最坏)

你可以做的是实现你自己的线程逻辑,前提是你有很多小任务,例如假设你想轮询十个你可以只使用一个 CPU 的东西。

【讨论】:

  • 我完全不介意放弃 CPU,我担心的是,如果没有其他线程取代它,System.currrentTimeInMilliseconds() 会将 CPU 带到 100%。所以事实上我想要其他人,我不想限制 CPU 检查是否发生超时。
  • 抱歉,我看错了问题。我假设你也想轮询一些 IO。
  • 我已经更新了我的答案,只是做超时,或者在超时的情况下执行非阻塞操作。
  • 我不想这么说,但你的解决方案似乎相当复杂,因为每一次民意调查都需要检查一次(如果我没看错的话)。我想在可能的 CPU 使用率方面,我并没有真正看到我之前所拥有的优势......
  • 是的,我在发表评论后才意识到这一点。这就是为什么在凌晨 5 点之前编码并不总是一件好事;)
【解决方案2】:

我认为使用等待/通知会更有效

boolean arrived;

public synchronized void waitForResponse(long timeout) throws InterruptedException, TimeoutException {
    long t0 = System.currentTimeMillis() + timeout;
    while (!arrived) {
        long delay = System.currentTimeMillis() - t0;
        if (delay < 0) {
            throw new TimeoutException();
        }
        wait(delay);
    }
}

public synchronized void responseArrived() {
    arrived = true;
    notifyAll();
}

【讨论】:

  • 它有时可能会失败(来自 wait() 的文档):“线程也可以在没有被通知、中断或超时的情况下唤醒 [...] 换句话说,等待应该总是在循环中发生"
  • 正确,这只是一个想法,而不是生产代码。有一个示例如何在 Thread.join(long millis) 中正确执行此操作
  • 另一个问题是您将强制每个循环成为任何延迟。所以举个例子,如果你使用 100ms,那么最小的时间增量是 100ms。
  • 循环只是对虚假唤醒的保护(参见 Object.wait API)。在现实生活中没有循环,waitForResponse 在等待时阻塞,在响应到达或超时时获取信号。
猜你喜欢
  • 1970-01-01
  • 2021-12-23
  • 1970-01-01
  • 2012-11-16
  • 2012-12-02
  • 1970-01-01
  • 2021-07-10
  • 2011-03-16
  • 2020-05-26
相关资源
最近更新 更多