我正在尝试使用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
答案 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的默认设置实际上可以更好地满足您的目标