使用onComplete时,限制Scala Future阻止

时间:2016-12-05 18:49:42

标签: scala callback deadlock throttling concurrent.futures

我正在尝试使用Scala Futures生成许多CPU密集型作业。因为他们是如此,我可能需要限制这些工作(线程)的创建。要做到这一点,我使用:

import java.util.concurrent.ArrayBlockingQueue
import scala.concurrent._

val numThread = sys.runtime.availableProcessors

import java.util.concurrent.ExecutorService
import java.util.concurrent.ArrayBlockingQueue

implicit val context = ExecutionContext.fromExecutorService(
    new ThreadPoolExecutor(
      numThread, numThread,
      0L, TimeUnit.SECONDS,
      new ArrayBlockingQueue[ Runnable ]( numThread ) {
        override def offer( e: Runnable ) = {
          put( e ); // Waiting for empty room
          true
        }
      })
     )

为了测试这个,我创建了两个非常简单的函数:

import scala.util.{ Try, Success, Failure }
import scala.util.Random

def longComputation() = {
  val id = Thread.currentThread().getId
  //blocking {
    println( s"Started thread: $id" )
    Thread.sleep( 500 )
    println( s"Finished thread: $id" )
  //}
  id
}

def processResult[T](r : Try[T]) = {
  blocking {
      r match {
        case Success( id ) => println( s"Thread result: $id" )
        case Failure( t )  => println( "An error has occured: " + t.getMessage )
       }
  }

}

然后我执行测试,以便通过多线程执行任务:

def main( args: Array[ String ] ) {


   val s = Stream.from( 0 )
   //s.foreach { x => println(x) ;  val f = Future( longComputation ) ; f.onComplete{ processResult } }

   s.foreach { x => 
     println(x) 
     val f = Future( longComputation )  
     val p = Promise[Long]()
     p completeWith f
     p.future.onComplete{ processResult } 
   }

   println("Finished")
   context.shutdown
 } 

当我执行此操作时,我得到了16个线程(CPU数量为8),它们已经完成启动,执行和打印。然后系统锁定,不执行任何其他操作。但是,如果我删除回调,则线程按预期执行 ad infinitum

上面我已尝试使用blocking并使用Promise。行为没有变化。所以我的问题是:如何在不阻止回调的情况下限制任务执行?如果这不可能,那么在线程(未来)中进行I / O是否可行?

欣赏任何指针。 TIA

2 个答案:

答案 0 :(得分:2)

程序运行陷入僵局。提供的threadPool具有固定大小,因此会发生以下情况: Future(longComputation)从线程池中分配一个线程并开始工作。完成后,onComplete从池中分配Thread以执行提供的功能。

鉴于工作所需的时间比完成工作要长,所以在某些时候,所有线程都在忙于工作。其中任何一个都完成,onComplete也需要一个线程,所以它请求执行者一个。工作无法完成,因为所有线程都忙,机器停止死锁。

我们可以通过向消费者提供预留资源来解决这种生产者 - 消费者的僵局。这样,工作受到固定大小的线程池的限制,但我们确保可以进一步处理任何已完成的工作。

我已将context重命名为fixedContext的代码段显示了使用单独的上下文来处理结果,解决了死锁问题。我也摆脱了Promise,除了代理未来之外,它没有发挥真正的功能。

val fixedContext = // same as in question
val singleThreadContext = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(1))
...
...
def main( args: Array[ String ] ) {

   val s = Stream.from( 0 )

   s.foreach { x => 
     println(x)
     val f = Future( longComputation )(fixedContext)  
     f.onComplete{ processResult }(singleThreadContext)
   }

   println("Finished")
   fixedContext.shutdown
 } 
}

答案 1 :(得分:1)

当一个线程完成longComputation时,它会尝试将作业放在队列上以执行回调,并被阻塞,因为队列已满。所以,最终,第一批"批次"作业完成,但所有线程仍然忙,等待队列调度回调,没有任何东西可用于拉出队列。

解决方案?从队列中删除限制。这样,尝试提交回调的线程就不会被阻止,并且可用于接收下一个任务。

您可能希望在生产者循环中插入一些内容以减慢它的速度,这样您的无限队列就不会占用所有内存。可能是Semaphore

val sem = new Semaphore(numThread*2)
def processResult[T](r : Try[T]) = blocking {
  r match {
    case Success( id ) => println( s"Thread result: $id" )
    case Failure( t )  => println( "An error has occured: " + t.getMessage )
  }
  sem.release
}

Stream.from(0).foreach { _ => 
  sem.acquire
  new Future(longComputation).onComplete(processResult)
}

您不需要自定义执行上下文 - scala的默认设置实际上可以更好地满足您的目标