【问题标题】:Computation with time limit有时间限制的计算
【发布时间】:2011-11-30 20:46:42
【问题描述】:

我正在尝试编写一个允许我在给定时间窗口中运行计算的构造。比如:

def expensiveComputation(): Double = //... some intensive math

val result: Option[Double] = timeLimited( 45 ) { expensiveComputation() }

这里timeLimited 将运行expensiveComputation,超时时间为45 分钟。如果达到超时,则返回None,否则将结果包装到Some

我正在寻找以下解决方案:

  • 在性能和内存方面相当便宜;
  • 将在当前线程中运行限时任务。

有什么建议吗?

编辑

我了解我最初的问题没有解决方案。假设我可以为计算创建一个线程(但我不喜欢使用线程池/执行器/调度程序)。最快、最安全、最干净的方法是什么?

【问题讨论】:

  • 是否可以检查expensiveComputation() 中的变量或在while(doIHaveTimeLef())... 之类的循环中运行昂贵的计算?
  • 理想情况下不会。我更喜欢通用解决方案...
  • @Jamil 当然这是一个“停止问题”。但不是经典版,不是吗?您的视野有限,因此问题变得可以确定。
  • Thread.stop() 的问题是它可能会使以前锁定的对象处于不一致的状态,对其他线程可见。只要您完全丢弃超时线程可能修改的任何内容,stop() 对您来说可能没问题。但是,如果您想从计算中获得一些复杂的结果,这可能并不容易实现。
  • 请注意,接受的解决方案不会停止正在运行的线程。事实上,这样做的 Java 方法已被弃用——有关更多信息,请参阅this。 Effective Java 中推荐的方法是有一个布尔标志来指示这是否已经超时,并在计算中定期检查它。

标签: java scala timeout


【解决方案1】:

如果您想在当前线程中运行任务并且不应该涉及其他线程,则必须检查expensiveComputation内的时间限制是否超过。例如,如果expensiveComputation 是一个循环,您可以在每次迭代后检查时间。

【讨论】:

  • 这意味着必须重写每一个不同的expensiveComputation...对我来说这是最坏的情况,如果我找不到更好的东西。
【解决方案2】:

如果您对expensiveComputation 的代码可以经常检查Thread.interrupted(),非常容易。但我想你不是。

我认为没有任何解决方案适用于任意expensiveComputation 代码。 问题是你准备有什么限制昂贵的计算。

您也有已弃用且非常不安全的Thead.stop(Throwable)。如果您的代码除了自己创建的对象之外没有修改任何对象,那么它可能会起作用。

【讨论】:

  • 我想尽可能少地改变expensiveComputations(最好不要改变任何东西)。
【解决方案3】:

在当前线程中??呼... 在计算的每一步之后检查 好吧,如果您的“昂贵的计算”可以分解为多个步骤或具有迭代逻辑,则您可以捕获开始的时间,然后在步骤之间定期检查。这绝不是一个通用的解决方案,但会起作用。

对于更通用的解决方案,您可以使用方面或注释处理,这些检查会自动在您的代码中添加这些检查。如果“检查”告诉您时间到了,则返回 None。

下面将使用注解和注解处理器快速思考 java 中的解决方案...

public abstract Answer{}
public class Some extends Answer {public Answer(double answer){answer=answer}Double answer = null;}
public class None extends Answer {}


//This is the method before annotation processing
@TimeLimit(45)
public Answer CalculateQuestionToAnswerOf42() {
 double fairydust = Math.Pi * 1.618;
 double moonshadowdrops = (222.21) ^5;
 double thedevil == 222*3;
 return new Answer(fairydust + moonshadowdrops + thedevil);
}

//After annotation processing
public Answer calculateQuestionToAnswerOf42() {
 Date start = new Date() // added via annotation processing;
 double fairydust = Math.Pi * 1.618; 
 if(checkTimeout(start, 45)) return None; // added via annotation processing;
 double moonshadowdrops = (222.21) ^5;
 if(checkTimeout(start, 45)) return None; // added via annotation processing;
 double thedevil == 222*3;
 if(checkTimeout(start, 45)) return None; // added via annotation processing;
 return new Answer(fairydust + moonshadowdrops + thedevil);
}

【讨论】:

    【解决方案4】:

    我看到这样的模式非常适合限时任务(Java 代码):

    try {
        setTimeout(45*60*1000); // 45 min in ms
        while (not done) {
           checkTimeout();
           // do some stuff
           // if the stuff can take long, again:
           checkTimeout();
           // do some more stuff
        }
        return Some(result);
    }
    catch (TimeoutException ex) {
        return None;
    }
    

    checkTimeout() 函数调用起来很便宜;您将它添加到代码中,以便合理地经常调用它,但不要太频繁。它所做的只是根据setTimeout() 设置的计时器值加上超时值检查当前时间。如果当前时间超过该值,checkTimeout() 会引发 TimeoutException

    我希望这个逻辑也可以在 Scala 中重现。

    【讨论】:

      【解决方案5】:

      只是一个想法:我对akka futures不是很熟悉。但也许可以将未来执行线程粘在当前线程上并使用带有超时的 akka 期货?

      【讨论】:

        【解决方案6】:

        据我所知,要么让出(计算调用某个调度程序)要么使用线程,它是从“外部”操纵的。

        【讨论】:

          【解决方案7】:

          如果你非常需要这个,你可以创建一个编译器插件,在循环和条件中插入检查块。然后这些检查块可以检查 Thread.isInterrupted() 并抛出异常以逃避。

          您可以使用注释,即@interruptible,来标记要增强的方法。

          【讨论】:

            【解决方案8】:

            运行给定的代码块或在超时时抛出异常:

            @throws(classOf[java.util.concurrent.TimeoutException])
            def timedRun[F](timeout: Long)(f: => F): F = {
            
              import java.util.concurrent.{Callable, FutureTask, TimeUnit}
            
              val task = new FutureTask(new Callable[F]() {
                def call() = f
              })
            
              new Thread(task).start() 
            
              task.get(timeout, TimeUnit.MILLISECONDS)
            }
            

            【讨论】:

              【解决方案9】:

              对于一个通用的解决方案(不必用 checkTimeout() 代码去乱扔你的每一个昂贵的计算)也许使用 Javassist。 http://www.csg.is.titech.ac.jp/~chiba/javassist/
              然后,您可以动态插入各种 checkTimeout() 方法。
              这是他们网站上的介绍文字:

              Javassist(Java 编程助手)使 Java 字节码操作变得简单。它是一个用于在 Java 中编辑字节码的类库;它使 Java 程序能够在运行时定义一个新类,并在 JVM 加载类文件时对其进行修改。与其他类似的字节码编辑器不同,Javassist 提供了两个级别的 API:源代码级别和字节码级别。如果用户使用源级 API,他们可以在不了解 Java 字节码规范的情况下编辑类文件。整个 API 仅使用 Java 语言的词汇设计。您甚至可以以源文本的形式指定插入的字节码; Javassist 即时编译它。另一方面,字节码级别的 API 允许用户像其他编辑器一样直接编辑类文件。

              面向方面的编程:Javassist 是一个很好的工具,可以将新方法添加到类中,并在调用方和被调用方双方插入 before/after/around 建议。

              反射:Javassist 的应用之一是运行时反射; Javassist 使 Java 程序能够使用元对象来控制对基础级对象的方法调用。不需要专门的编译器或虚拟机。

              【讨论】:

                猜你喜欢
                • 2019-06-25
                • 2013-12-18
                • 2015-12-13
                • 2023-01-15
                • 1970-01-01
                • 1970-01-01
                • 2011-11-06
                • 1970-01-01
                • 2011-07-25
                相关资源
                最近更新 更多