如何创建块感知执行上下文?

时间:2016-01-28 15:27:23

标签: java multithreading scala concurrency executioncontext

出于某种原因,我无法解决这个问题。我有一个运行Play的应用程序,呼叫Elastic Search。作为我设计的一部分,我的服务使用包含scala future的Java API,如此blog post所示。我已经更新了该帖子中的代码,提示它将执行一些阻塞I / O的ExecutionContext:

    import scala.concurent.{blocking, Future, Promise}
    import org.elasticsearch.action.{ActionRequestBuilder, ActionListener, ActionResponse }
    def execute[RB <: ActionRequestBuilder[_, T, _, _]](request: RB): Future[T] = {
        blocking {
            request.execute(this)
            promise.future
        }
    }

构建要发送给ES的查询的实际服务将executionContext作为构造函数参数,然后用于调用弹性搜索。我这样做是为了让播放使用的全局执行上下文不会让它的线程被ES的阻塞调用所束缚。 This S.O. comment提到只有全局上下文才会阻塞,所以这让我不得不创建自己的。在同一个帖子/答案中有很多关于使用ForkJoin池的信息,但是我不确定如何使用those docs中的内容并将其与{{{{}}结合起来。 3}}创建一个响应阻塞提示的执行上下文。

我认为我遇到的一个问题是,我不确定如何首先响应阻止上下文?我正在阅读hints in the blocking documentation,它使用的示例是一个无限的线程缓存:

  

请注意,在这里我更喜欢使用无界&#34;缓存的线程池&#34;,因此它没有限制。在阻止I / O时,我们的想法是你必须拥有足够的线程来阻止它。但是如果无界限太多,取决于用例,你可以稍后对其进行微调,这个样本的想法就是让你滚动。

这是否意味着使用我的ForkJoin支持的线程池,我应该尝试在处理非阻塞I / O时使用缓存线程并创建一个新线程来阻止IO?或者是其他东西?几乎我在网上找到的关于使用单独线程池的所有资源都倾向于执行best practices,即:

  

如何调整各种线程池在很大程度上取决于您的个人应用程序,超出了本文的范围。

我知道这取决于你的应用程序,但在这种情况下,如果我只想创建某种类型的阻塞感知ExecutionContext并理解管理线程的合适策略。如果Context专门用于应用程序的单个部分,那么我应该只是制作一个固定的线程池大小而不是首先使用/忽略blocking关键字吗?

我倾向于絮絮叨叨,所以我会试着打破我在答案中寻找的东西:

  1. 代码!阅读所有这些文档仍然让我觉得我觉得无法编写阻塞感知的上下文,我真的很感激一个例子。
  2. 关于如何处理阻塞线程的任何链接或提示,即无休止地为它们创建一个新线程,检查可用线程的数量,如果太多则拒绝,其他一些策略
  3. 我不是在寻找性能提示,我知道我只会通过测试得到它,但我无法测试我是否能够弄清楚如何对上下文进行编码#39; s首先!我确实找到了Neophytes guide does,但我错过了关于blocking的关键部分。
  4. 对不起,这里有一个很长的问题,我只是想让你了解一下我在看什么,并且我一直试图绕过这一天超过一天需要一些外面的帮助。

    编辑:为了清楚说明,ElasticSearch Service的构造函数签名是:

    //Note that these are not implicit parameters!
    class ElasticSearchService(otherParams ..., val executionContext: ExecutionContext)
    

    在我的应用程序中启动代码我有类似的东西:

    object Global extends GlobalSettings {
        val elasticSearchContext = //Custom Context goes here
        ...
        val elasticSearchService = new ElasticSearchService(params, elasticSearchContext);
        ...
    }
    

    我也正在阅读an example of ForkJoins vs threadpools,但尚未看到有关阻止提示的任何内容,我怀疑我可能需要查看源代码,看看它们是否扩展了BlockContext特征。 / p>

1 个答案:

答案 0 :(得分:1)

所以我挖掘了文档和Play's best practices,因为我正在处理的情况是

  

在某些情况下,您可能希望将工作分派给其他线程池。这可能包括CPU繁重的工作或IO工作,例如数据库访问。要做到这一点,首先应该创建一个线程池,这可以在Scala中轻松完成:

并提供一些代码:

object Contexts {
    implicit val myExecutionContext: ExecutionContext = Akka.system.dispatchers.lookup("my-context")
}

上下文来自Akka,所以我在那里搜索他们提供的默认和类型的上下文,最终导致我documentation on dispatchers。默认值为ForkJoinPool,其管理块的默认方法是调用managedBlock(blocker)。这导致我阅读说明的文件:

  

根据给定的阻止程序阻止。如果当前线程是ForkJoinWorkerThread,则此方法可能会安排在必要时激活备用线程,以确保在当前线程被阻塞时具有足够的并行性。

所以看起来如果我有一个ForkJoinWorkerThread那么我认为我想要的行为将会发生。再看一下ForkJoinPool的来源,我注意到默认的线程工厂是:

val defaultForkJoinWorkerThreadFactory: ForkJoinWorkerThreadFactory = juc.ForkJoinPool.defaultForkJoinWorkerThreadFactory

这对我来说意味着如果我在Akka中使用默认值,那么我将获得一个以我期望的方式处理阻塞的上下文。

因此,再次阅读Akka文档似乎指定我的上下文:

my-context {
  type = Dispatcher
  executor = "fork-join-executor"
  fork-join-executor {
    parallelism-min = 8
    parallelism-factor = 3.0
    parallelism-max = 64
    task-peeking-mode = "FIFO"
  }
  throughput = 100
}

将是我想要的。

当我在源代码中搜索时,我做了一些寻找blocking或调用managedBlock的用法,并找到了覆盖ThreadPoolBuilder中的ForkJoin行为的示例

private[akka] class AkkaForkJoinWorkerThread(_pool: ForkJoinPool) extends ForkJoinWorkerThread(_pool) with BlockContext {
    override def blockOn[T](thunk: ⇒ T)(implicit permission: CanAwait): T = {
      val result = new AtomicReference[Option[T]](None)
      ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker {
        def block(): Boolean = {
          result.set(Some(thunk))
          true
        }
        def isReleasable = result.get.isDefined
      })
      result.get.get // Exception intended if None
    }
  }

哪个似乎就像我最初要求的那样,是一个如何制作实现BlockContext的例子。该文件还有代码显示如何创建ExecutorServiceFactory,这是我所相信的 是executor部分配置的引用。所以我想如果我想要做什么我会怎么做 一个完全自定义的上下文将扩展某种类型的WorkerThread并编写我自己的使用自定义workerthread的ExecutorServiceFactory,然后在属性中指定完全限定的类名,如this post advises

我可能会选择使用Akka的forkjoin :)