Play框架期货未由默认调度程序并行化

时间:2019-10-09 14:36:36

标签: multithreading scala playframework akka executioncontext

我正在尝试在Play应用中测试ExecutionContext的行为,发现使用默认调度程序通过调用{{1}时,我无法达到任何程度的并行度},as.dispatcher或将默认执行上下文作为参数传递给我的Controller类:

as.dispatchers.lookup("akka.actor.default-dispatcher")

我以here中提供的播放示例为基础。并添加/更改以下配置:

路线

class HomeController @Inject()(cc: ControllerComponents)(implicit ec: ExecutionContext)

common.conf

GET    /futures    controllers.HomeController.testFutures(dispatcherId: String)

HomeController

akka {
  my-dispatcher {
    executor = "fork-join-executor"
    fork-join-executor {
      # vm-cores = 4
      parallelism-min = 4

      parallelism-factor = 2.0

      # 2x vm-cores
      parallelism-max = 8
    }
  }

  actor.default-dispatcher {
    executor = "fork-join-executor"
    fork-join-executor {
      # vm-cores = 4
      parallelism-min = 4

      parallelism-factor = 2.0

      # 2x vm-cores
      parallelism-max = 8
    }
  }
}

由于某些原因,对@Singleton class HomeController @Inject()(cc: ControllerComponents, as: ActorSystem) extends AbstractController(cc) { import HomeController._ def testFutures(dispatcherId: String) = Action.async { implicit request => implicit val dispatcher = as.dispatchers.lookup(dispatcherId) Future.sequence((0 to 10).map(i => Future { val time = 1000 + Random.nextInt(200) log.info(s"Sleeping #$i for $time ms") Thread.sleep(time) log.info(s"Awakening #$i") })).map(_ => Ok("ok")) } } 默认调度程序)的调用不会并行化并产生以下输出:

http://localhost:9000/futures?dispatcherId=akka.actor.default-dispatcher

但是对此[info] c.HomeController - Sleeping #0 for 1044 ms [info] c.HomeController - Awakening #0 [info] c.HomeController - Sleeping #1 for 1034 ms [info] c.HomeController - Awakening #1 [info] c.HomeController - Sleeping #2 for 1031 ms [info] c.HomeController - Awakening #2 [info] c.HomeController - Sleeping #3 for 1065 ms [info] c.HomeController - Awakening #3 [info] c.HomeController - Sleeping #4 for 1082 ms [info] c.HomeController - Awakening #4 [info] c.HomeController - Sleeping #5 for 1057 ms [info] c.HomeController - Awakening #5 [info] c.HomeController - Sleeping #6 for 1090 ms [info] c.HomeController - Awakening #6 [info] c.HomeController - Sleeping #7 for 1165 ms [info] c.HomeController - Awakening #7 [info] c.HomeController - Sleeping #8 for 1173 ms [info] c.HomeController - Awakening #8 [info] c.HomeController - Sleeping #9 for 1034 ms [info] c.HomeController - Awakening #9 [info] c.HomeController - Sleeping #10 for 1056 ms [info] c.HomeController - Awakening #10 的调用(使用另一个调度程序)并行化了正确性并产生了以下输出。

http://localhost:9000/futures?dispatcherId=akka.my-dispatcher

为什么会发生这种情况?

1 个答案:

答案 0 :(得分:1)

我认为行为是由akka.actor.default-dispatcher类型的BatchingExecutor给出的,这将在诸如map/flatmap之类的操作情况下通过在同一线程中执行它们来尝试优化避免不必要的日程安排。在我们要阻止的情况下,我们可以用scala.concurrent.blocking (Thread.sleep (time))的提示来表示它,这样,在ThreadLocal[BlockContext]中存储了一个标记,它指示了阻止的意图,并且不应用优化,但是将操作抛出另一个线程。

如果您为此Thread.sleep(time)更改此行scala.concurrent.blocking(Thread.sleep(time)),则会获得所需的行为

@Singleton
class HomeController @Inject()(cc: ControllerComponents, as: ActorSystem) extends AbstractController(cc) {
  import HomeController._

  def testFutures(dispatcherId: String) = Action.async { implicit request =>
    implicit val dispatcher = as.dispatchers.lookup(dispatcherId)
    Future.sequence((0 to 10).map(i => Future {
      val time = 1000 + Random.nextInt(200)
      log.info(s"Sleeping #$i for $time ms")
      scala.concurrent.blocking(Thread.sleep(time))
      log.info(s"Awakening #$i")
    })).map(_ => Ok("ok"))
  }
}
[info] play.api.Play - Application started (Dev) (no global state)
Sleeping #0 for 1062 ms
Sleeping #1 for 1128 ms
Sleeping #2 for 1189 ms
Sleeping #3 for 1105 ms
Sleeping #4 for 1169 ms
Sleeping #5 for 1178 ms
Sleeping #6 for 1057 ms
Sleeping #7 for 1003 ms
Sleeping #8 for 1164 ms
Sleeping #9 for 1029 ms
Sleeping #10 for 1005 ms
Awakening #7
Awakening #10
Awakening #9
Awakening #6
Awakening #0
Awakening #3
Awakening #1
Awakening #8
Awakening #4
Awakening #5
Awakening #2