问题 为什么新线程而不是未来{...}


这个答案 指示如何转换 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

我的问题与评论中提出的问题相同:

怎么了? future { jfuture.get }?为什么你使用额外的线程与Promise结合?

答案如下:

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

我不确定我理解这个解释。重申:

怎么了? future { jfuture.get }?在手中创建一个新线程并在那里阻塞,是不是在未来阻止?如果没有,它有什么不同?


8974
2018-01-17 15:52


起源

究竟你不懂什么?阻塞线程意味着什么? - Alexei Kaigorodov
@AlexeiKaigorodov我有点修改了我的问题:出了什么问题 future { jfuture.get }?在手中创建一个新线程并在那里阻塞,是不是在未来阻止?如果没有,它有什么不同? - Dominykas Mostauskis
是的,阻止未来的内容是手动创建一个新的线程并阻塞它。转换为的想法 scala.concurrent.Future 是通过使用完全避免阻止 onComplete 代替 get。 - Alexei Kaigorodov


答案:


两者之间几乎没有区别 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

10秒有点太长了 2+2 评价。

所有其他 futures和同一执行上下文中的所有actor将停止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

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


8
2018-01-17 17:04



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


它实际上非常简单。 scala.concurrent.Promise 是一个具体的实现 Future,注定是异步计算。

当你想转换时,用 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 Thread 和a new Runnable 要么 new Callable 允许计算执行/独占子线程。

在示例@senia中给出:

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

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

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


7
2018-01-17 16:55



我的印象是产生一个新线程并执行 somethingThatBlocks() 在它中与未来相同(somethingThatBlocks())。我的问题主要是关于这是否属实,如果不是,它有何不同? - Dominykas Mostauskis
所以一般情况下,不推荐在未来的默认执行上下文中进行冗长的计算? - Denis Tulskiy
@DominykasMostauskis:不,默认执行上下文有一个线程池,并选择其中一个来运行未来的代码。它不会每次都跨越新线程,因为创建线程是一项昂贵的操作,线程是有限的资源。 - Denis Tulskiy