【问题标题】:Why new thread instead of future {...}为什么新线程而不是未来 {...}
【发布时间】:2014-02-07 00:48:42
【问题描述】:

This answer 指示如何将java.util.concurrent.Future 转换为scala.concurrent.Future,同时管理阻塞发生的位置:

import java.util.concurrent.{Future => JFuture}
import scala.concurrent.{Future => SFuture}

val jfuture: JFuture[T] = ???
val promise = Promise[T]()
new Thread(
  new Runnable {
    def run() { promise.complete(Try{ jfuture.get }) }
  }
).start
val future = promise.future

我的问题与cmets中提出的问题相同:

future { jfuture.get } 有什么问题?为什么你使用一个额外的线程来结合 Promise?

回答如下:

它会阻塞线程拉动中的线程。如果您为此类期货配置了 ExecutionContext,那很好,但默认的 ExecutionContext 包含与您拥有的处理器一样多的线程。

我不确定我是否理解解释。重申:

future { jfuture.get } 有什么问题?在未来内部阻塞与手动创建一个新线程并在那里阻塞不一样吗?如果不是,有什么不同?

【问题讨论】:

  • 你到底有什么不明白的?线程阻塞是什么意思?
  • @AlexeiKaigorodov 我稍微修改了我的问题:future { jfuture.get } 有什么问题?在未来内部阻塞与手动创建一个新线程并在那里阻塞不一样吗?如果不是,有什么不同?
  • 是的,在未来阻塞是很糟糕的,因为手动创建一个新线程并在那里阻塞。转换为scala.concurrent.Future 的想法是通过使用onComplete 而不是get 来完全避免阻塞。

标签: java multithreading scala blocking future


【解决方案1】:

future { jfuture.get }future { future { jfuture.get }} 几乎没有区别。

默认线程池中的线程数与您拥有的处理器数一样多。

使用jfuture.get,您将获得 1 个线程阻塞。

假设您有 8 个处理器。另外让我们假设每个jfuture.get 需要10 秒。现在创建 8 个future { jfuture.get }

val format = new java.text.SimpleDateFormat("HH:mm:ss").format(_: Date)

val startTime = new Date
(1 to 8) map {_ => future{ Thread.sleep(10000) }}
future{
  2+2
  println(s"2+2 done. Start time: ${format(startTime)}, end time: ${format(new Date)}")
}

// 2+2 done. Start time: 20:48:18, end time: 20:48:28

对于2+2 评估来说,10 秒有点太长了。

所有其他futures 和同一执行上下文中的所有参与者都将停止 10 秒。

使用额外的执行上下文:

object BlockingExecution {
  val executor = ExecutionContext.fromExecutor(new ForkJoinPool(20))
}

def blockingFuture[T](f: => T) = {
  future( f )(BlockingExecution.executor)
}

val startTime = new Date
(1 to 8) map {_ => blockingFuture{ Thread.sleep(10000) }}
future{
  2+2
  println(s"2+2 done. Start time: ${format(startTime)}, end time: ${format(new Date)}")
}

// 2+2 done. Start time: 21:26:18, end time: 21:26:18

您可以使用new Thread(new Runnable {... 实现blockingFuture,但额外的执行上下文允许您限制线程数。

【讨论】:

  • 计算 2+2 的未来需要在其他未来之后执行,这并没有内在的原因。 (您只是要求异步计算 9 件事。)如果您将 Future { Thread.sleep(10000) } 更改为 Future { blocking { Thread.sleep(10000) } },2+2 未来将立即执行。我的理解是blocking 方法给出了一个提示,以便线程池首先继续处理其他期货。我还没有找到任何好的资源来准确描述这里发生的事情。
  • @Patrick 这是真的。当使用blocking 时,全局线程池将为该计算生成一个新线程,这样它就不会用完线程。更多信息在这里:docs.scala-lang.org/overviews/core/futures.html 请参阅“阻止”部分
【解决方案2】:

其实很简单。 scala.concurrent.PromiseFuture 的具体实现,注定是异步计算。

当您想使用jfuture.get 进行转换时,您正在运行阻塞计算并输出立即解析的scala.concurrent.Future

Thread 将阻塞,直到 jfuture 内部的计算完成。 get 方法是阻塞的。

阻塞意味​​着在计算完成之前Thread 内部不会发生任何其他事情。你基本上垄断了Thread,看起来像while 循环间歇性地检查结果。

while (!isDone() && !timeout) {
   // check if the computation is complete
}

具体来说:

val jfuture: JFuture[T] = ??? // some blocking task

当无法避免阻塞时,通常的做法是生成 new Threadnew Runnablenew Callable 以允许计算执行/独占子线程。

在@senia 给出的示例中:

new Thread(new Runnable { def run() {
  promise.complete(Try{ jfuture.get })
}}).start

这与future {jfuture.get} 有何不同?它不会阻止您的默认ExecutionContext,由 Scala 提供,它拥有与机器处理器一样多的线程。

这意味着代码中的所有其他未来将始终必须等待 future { jfuture.get } 完成,因为整个上下文都被阻止了。

【讨论】:

  • 我的印象是生成一个新线程并在其中执行somethingThatBlocks() 与future(somethingThatBlocks()) 相同。我的问题主要是关于这是否属实,如果不是,有什么不同?
  • 所以一般来说不建议在默认执行上下文的未来中进行冗长的计算?
  • @DominykasMostauskis:不,默认执行上下文有一个线程池,并选择其中一个来运行未来内部的代码。它不会每次都跨越新线程,因为创建线程是一项昂贵的操作,而且线程是有限的资源。
猜你喜欢
  • 2014-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-17
  • 2013-05-13
  • 1970-01-01
  • 2013-10-31
相关资源
最近更新 更多