繁体   English   中英

如何在超时开始之前适当地阻塞线程?

[英]How can I properly block a thread until timeout starts?

我想并行运行多个任务,直到经过一定时间。 让我们假设这些线程占用大量CPU和/或可能无限期地阻塞。 超时后,线程应立即中断,并且主线程应继续执行,无论未完成或仍在运行的任务如何。

我已经看到很多问题在问这个问题,并且答案总是相似的,通常都遵循“为任务创建线程池,启动它,在超时时加入它”的思路。

问题出在“开始”和“加入”部分之间。 一旦允许该池运行,它可能会占用CPU,并且直到我将其恢复之前,超时都不会开始。

我尝试了Executor.invokeAll,发现它不完全满足要求。 例:

    long dt = System.nanoTime ();
    ExecutorService pool = Executors.newFixedThreadPool (4);
    List <Callable <String>> list = new ArrayList <> ();
    for (int i = 0; i < 10; i++) {
        list.add (new Callable <String> () {
            @Override
            public String call () throws Exception {
                while (true) {
                }
            }
        });
    }
    System.out.println ("Start at " + (System.nanoTime () - dt) / 1000000 + "ms");
    try {
        pool.invokeAll (list, 3000, TimeUnit.MILLISECONDS);
    }
    catch (InterruptedException e) {
    }
    System.out.println ("End at " + (System.nanoTime () - dt) / 1000000 + "ms");

1ms开始
结束于3028ms

这种延迟(27 ms的延迟)似乎还不错,但是无限循环很容易被打破-实际程序的体验容易十倍。 我的期望是即使在重负载下也可以非常高精度地满足超时请求(我正在考虑硬件中断,它应该一直有效)。

这是我的特定程序中的一个主要难题,因为它需要相当准确地注意某些超时(例如,大约100毫秒,如果可能更好)。 但是,启动缓冲池通常要花费400毫秒,直到我重新获得控制权,超过了最后期限。

我有点困惑为什么几乎没有提到这个问题。 我看到的大多数答案肯定都受此困扰。 我想这通常可以接受,但就我而言,这是不可接受的。

是否有一种干净且经过测试的方式来解决此问题?

编辑添加:

我的程序涉及GC,尽管规模不大。 为了进行测试,我重写了上面的示例,发现给出的结果非常不一致,但平均而言比以前明显差。

    long dt = System.nanoTime ();
    ExecutorService pool = Executors.newFixedThreadPool (40);
    List <Callable <String>> list = new ArrayList <> ();
    for (int i = 0; i < 10; i++) {
        list.add (new Callable <String> () {
            @Override
            public String call () throws Exception {
                String s = "";
                while (true) {
                    s += Long.toString (System.nanoTime ());
                    if (s.length () > 1000000) {
                        s = "";
                    }
                }
            }
        });
    }
    System.out.println ("Start at " + (System.nanoTime () - dt) / 1000000 + "ms");
    try {
        pool.invokeAll (list, 1000, TimeUnit.MILLISECONDS);
    }
    catch (InterruptedException e) {
    }
    System.out.println ("End at " + (System.nanoTime () - dt) / 1000000 + "ms");

1ms开始
结束于1189ms

invokeAll应该可以正常工作。 但是,编写任务以正确响应中断至关重要。 捕获InterruptedException时,它们应立即退出。 如果您的代码正在捕获IOException,则每个此类catch-block都应带有类似以下内容的代码:

} catch (InterruptedIOException e) {
    logger.log(Level.FINE, "Interrupted; exiting", e);
    return;
}

如果您使用Channels ,则将希望以相同的方式处理ClosedByInterruptException

如果执行的耗时操作没有捕获上述异常,则需要定期检查Thread.interrupted() 显然,检查频率越高越好,尽管这会导致收益递减。 (意味着,在任务中的每条语句之后检查它可能都没有用。)

if (Thread.interrupted()) {
    logger.fine("Interrupted; exiting");
    return;
}

在您的示例代码中,您的Callable根本没有检查中断状态,因此我猜测它永远不会退出。 中断实际上并不会停止线程; 它只是向线程发出信号,表明它应该按照自己的条件终止。

使用VM选项-XX:+PrintGCDetails ,我发现GC的运行次数很少,但延迟时间比预期的-XX:+PrintGCDetails多。 这种延迟恰好与我经历的峰值相吻合。

对观察到的行为的平凡而悲伤的解释。

暂无
暂无

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

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