工作/任务窃取线程池执行器

8
在我的项目中,我正在构建一个Java执行框架,该框架接收来自客户端的工作请求。工作(大小各异)被拆分成一组任务,然后排队等待处理。有单独的队列来处理每种类型的任务,并且每个队列都与ThreadPool相关联。 ThreadPool配置方式是优化引擎整体性能的方式。
这种设计有助于我们有效地平衡负载,避免大型请求占用系统资源。但是,在某些时候,当某些队列为空并且它们各自的线程池处于空闲状态时,解决方案变得无效。
为了使其更好,我考虑实现一种工作/任务窃取技术,以便负载较重的队列可以从其他ThreadPools获得帮助。但是,这可能需要实现自己的Executor,因为Java不��许将多个队列与ThreadPool关联,并且不支持工作窃取概念。
阅读有关Fork/Join的内容,但那似乎不适合我的需求。如果有任何建议或构建此解决方案的替代方法,那将非常有帮助。
谢谢 Andy

1
你应该考虑如何让所有的CPU保持繁忙状态。如果你能充分利用CPU,那么一些线程处于空闲状态也没关系。 - Peter Lawrey
如果您的线程池中有与 CPU 数量相同的线程,任何一个单独的线程池都可以“窃取”所有 CPU,即使所有其他线程池都处于空闲状态。 - Peter Lawrey
@PeterLawrey - 这是真的,但如果有很多线程池,那么如果所有线程池中的所有线程同时工作,您的性能可能会很差。 - jtahlborn
每个任务都依赖于大量的参考数据进行处理,因此CPU并不总是繁忙。此外,历史请求队列是我遇到困难的地方。这些请求被视为低优先级请求,但是当有空余容量时,我们打算全力处理它们。在峰值时间,共有35个线程在处理,16核心的计算机只能达到65%的利用率,因此我认为CPU良好。当负载模式关闭时,我想增加吞吐量。 - Anand Nadar
@jtahlborn,如果这样的话,您最好只有较少的池。我通常只有一个或两个。 ;) - Peter Lawrey
@AnandNadar,听起来你实际上是在尝试通过线程间接管理另一个资源,即数据库。 - Peter Lawrey
3个回答

4

Executors.newWorkStealingPool

Java 8提供了在Executors类中的工厂和实用方法:Executors.newWorkStealingPool

这是一个工作窃取线程池的实现,我相信这正是您所需要的。


我唯一看到的缺点是它会按需创建新的ForkJoin线程,而不是从全局池(可以是公共池或客户端可以传递的池)中借用这些线程。 - Rohit Banga

2

你是否考虑过ForkJoinPool?这个fork-join框架采用了很好的模块化方式,因此你可以直接使用工作窃取线程池。


1
阅读了API文档,但仍然无法弄清它与常规的ThreadPoolExecutor有何不同。可能是缺少一些细微的方面。 - Anand Nadar
是的,我明白了,你现在拥有的是一个分区方案,而你想要让它变得更加灵活--让分区边界根据工作负载进行移动。 "工作窃取"可能是涉及细粒度任务颗粒的更专业的术语--一个线程上执行的任务生成子任务并将其推送到自己的双端队列中,以便其他线程可以窃取它的工作。因此,也许如果你通过术语“线程池分区”进行研究,你会找到适合你情况的东西。 - Marko Topolnik

1

你可以实现一个自定义的BlockingQueue实现(我认为你主要需要实现offer()take()方法),它由一个“主”队列和0个或多个辅助队列支持。如果主备队列非空,则take总是从主备队列中获取,否则它可以从辅助队列中获取。

事实上,最好有一个池,所有工作人员都可以访问所有队列,但“偏爱”特定队列。通过为不同的工作人员分配不同的优先级,您可以得出最佳工作比率。在完全加载的系统中,您的工作人员应该以最佳比率工作。在负载不足的系统中,您的工作人员应该能够帮助其他队列。


这似乎是一个不错的想法,我倾向于尝试一下POC。 - Anand Nadar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接