【问题标题】:Java: Running potentially blocking codeJava:运行可能阻塞的代码
【发布时间】:2017-02-15 00:50:25
【问题描述】:

我正在开发一个小游戏(Java,LibGdx),玩家在其中使用预定义的代码行填充完形填空样式的函数。然后,游戏将编译代码并运行一个小型测试套件,以验证该函数是否完成了它应该做的事情。

编译和运行代码已经可以了,但是我遇到了检测无限循环的问题。考虑以下函数:

// should compute the sum of [1 .. n]
public int foo(int n) {
    int i = 0;
    while (n > 0) {
        i += n;
        // this is the place where the player inserts one of many predefined lines of code
        // the right one would be: n--;
        // but the player could also insert something silly like: i++;
    }
    return i;
}

请注意,实际使用的函数可能更复杂,一般无法确保不会出现无限循环。

目前我正在使用ExecutorService 在线程中运行小型测试套件(为每个函数提供),设置超时以中止等待以防线程卡住。这样做的问题是,陷入无限循环的线程将永远在后台运行,这当然会在某些时候对游戏性能产生相当大的影响。

// TestClass is the compiled class containing the function above and the corresponding test suite
Callable<Boolean> task = new Callable<Boolean>() {
    @Override
    public Boolean call() throws Exception {
        // call the test suite
        return new TestClass().test();
    }
};
Future<Boolean> future = executorService.submit(task);
try {
    Boolean result = future.get(100, TimeUnit.MILLISECONDS);
    System.out.println("result: " + (result == null ? "null" : result.toString()));
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
} catch (TimeoutException e) {
    e.printStackTrace();
    future.cancel(true);
}

我现在的问题是:如何优雅地结束在无限循环中意外旋转的线程?

*编辑为了澄清为什么在这种情况下,防止无限循环是不可能/可行的:函数、它们的测试套件和填补空白的行是从磁盘加载的。将有数百个函数可以插入至少两行代码。玩家可以将任何线拖入任何间隙。确保函数间隙/代码行的组合不会产生无限循环甚至运行时间超过超时所需的工作随着函数的数量呈指数增长。这很快就到了没有人有时间手动检查所有这些组合的地步。此外,一般来说,由于停机问题,要确定一个函数是否会及时完成几乎是不可能的。

【问题讨论】:

    标签: java multithreading loops


    【解决方案1】:

    在同一个进程中没有线程的“优雅终止”这样的事情。终止的线程可能会留下不一致的共享内存状态。

    您可以组织事物以使每个任务在其自己的 JVM 中启动,或者使用已弃用的 Thread.stop() 方法强制终止。

    另一种选择是在生成的代码中插入检查,但这需要更多的努力才能正确实施。

    【讨论】:

    • 感谢您的指示 :) 不幸的是,Thread.stop() 不再起作用了。你有关于如何在另一个 JVM 中运行任务的任何信息吗? (我也真的担心这会影响性能......)
    • Thread.stop() 确实有效,试试吧。子 JVM 使用 Runtime.exec() 启动。如果你有很多小任务,它会影响性能。
    • 没错,它有效。唯一不起作用的是停止我提供给 ExecutorService 的线程 :) 现在我只需要一个设置,我可以控制线程同时仍然能够检测到超时。 ExecutorService 没有给我线程对象。关于如何做到这一点的任何想法?
    • 这将比你目前所做的更肮脏。您可以提交一个自定义任务,将currentThread 保存到其字段并捕获ThreadDeath 异常,因此它实际上不会杀死线程,只是中止任务。您的外部代码将读取 currentThread 字段和 stop 它。
    • 哇,这终于奏效了:D 我同意这很脏。我能提出的唯一借口是,在这个特定的用例中,一旦超时,包含所有数据的实际任务就完全无关紧要了。谢谢你对我的耐心:)
    【解决方案2】:

    正确的方法是改变设计并避免永无止境的循环。

    目前,在您的循环中,您可以通过以下方式检查线程是否以某种方式中断:isInterrupted() 甚至 isAlive()

    如果是你就退出。

    【讨论】:

    • 这意味着在编译代码之前将这些检查注入每个循环,对吗?理论上这是可能的,但看起来很丑……我想知道我是否可以从函数本身的外部做一些事情,比如在调用测试套件时……你有什么首先如何避免无限循环的想法?这确实是最干净的解决方案,但我不确定这个问题是否可行......(请参阅我的问题编辑)
    • 线程应该负责检查是否已经从外部停止。这是在多线程中编写代码的正确方法,尤其是在线程要执行阻塞且非常长的任务时。我明白你的意思,你有大量的代码需要改变。看看这里stackoverflow.com/questions/10961714/… 是一个更完整的答案,但不确定你在寻找什么。
    【解决方案3】:

    如果不希望有一个永无止境的循环是不正常的。

    为了解决这个问题你可以在循环中添加一个计数器,如果达到限制就可以退出。

    int counter = 0;
    while (n > 0) {
        counter++;
        if (counter > THRESHOLD) {
           break; 
        }
        i += n;
        // this is the place where the player inserts one of many predefined lines of code
        // the right one would be: n--;
        // but the player could also insert something silly like: i++;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-18
      • 1970-01-01
      相关资源
      最近更新 更多