您的测试结构不正确,无法检验您的假设。
如果你过去文档中的这一部分 http://www.playframework.com/documentation/2.3.1/ThreadPools你会看到 Play 有一些线程池/执行上下文。对于你的问题来说,最重要的是默认线程池以及它与您的操作所服务的 HTTP 请求有何关系。
正如文档所描述的,默认线程池是所有应用程序代码默认运行的地方。 IE。所有操作代码,包括所有Future
的(没有显式定义自己的执行上下文),将在此执行上下文/线程池中运行。所以用你的例子:
def sync = Action {
// *** import Contexts.blockingPool
// *** Future {
// *** Thread.sleep(100)
// ***}
Ok("Done")
}
您操作中的所有代码not评论者// ***
将运行在默认线程池。
IE。当请求被路由到您的操作时:
- the
Future
与Thread.sleep
将被分派到您的自定义执行上下文
- 然后无需等待
Future
完成(因为它在自己的线程池中运行[Context.blockingPool
] 因此不会阻塞默认线程池上的任何线程)
- your
Ok("Done")
语句被评估并且客户端收到响应
- 大约。收到响应后 100 毫秒,您的
Future
完成
因此,为了解释您的观察结果,当您同时发送 100 个请求时,Play 将很乐意接受这些请求,路由到您的控制器操作(在默认线程池),发送到您的Future
然后回复客户。
默认池的默认大小为
play {
akka {
...
actor {
default-dispatcher = {
fork-join-executor {
parallelism-factor = 1.0
parallelism-max = 24
}
}
}
}
}
每个核心使用 1 个线程,最多 24 个。
鉴于你的行动几乎没有什么作用(不包括Future
),您将能够毫不费力地处理每秒 1000 个请求。你的Future
然而,将需要更长的时间来处理积压,因为您正在阻塞自定义池中的唯一线程(blockingPool
).
如果您使用我稍微调整过的操作版本,您将在日志输出中看到证实上述解释的内容:
object Threading {
def sync = Action {
val defaultThreadPool = Thread.currentThread().getName;
import Contexts.blockingPool
Future {
val blockingPool = Thread.currentThread().getName;
Logger.debug(s"""\t>>> Done on thread: $blockingPool""")
Thread.sleep(100)
}
Logger.debug(s"""Done on thread: $defaultThreadPool""")
Results.Ok
}
}
object Contexts {
implicit val blockingPool: ExecutionContext = Akka.system.dispatchers.lookup("blocking-pool-context")
}
您的所有请求都会首先得到迅速处理,然后您的Future
之后就一一完成了。
总而言之,如果您确实想测试 Play 如何在只有一个线程处理请求的情况下处理许多并发请求,那么您可以使用以下配置:
play {
akka {
akka.loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"]
loglevel = WARNING
actor {
default-dispatcher = {
fork-join-executor {
parallelism-min = 1
parallelism-max = 1
}
}
}
}
}
您可能还想添加Thread.sleep
像这样的操作(以减慢默认线程池的孤独线程速度)
...
Thread.sleep(100)
Logger.debug(s"""<<< Done on thread: $defaultThreadPool""")
Results.Ok
}
现在您将有 1 个用于请求的线程和 1 个用于您的Future
的。
如果您以高并发连接运行此程序,您会注意到客户端会在 Play 逐一处理请求时阻塞。这就是你期望看到的......