h2o.ai源码解析(3)—CPU管理
1. 前言
本系列中的第一篇中给出了h2o.ai的整体介绍。其整体架构如下:
在上图中核心基础设施(Core infrastructure)主要包含内存管理(基础数据结构)和CPU管理(任务调度)两部分。本篇先介绍任务调度部分
2. CPU管理
h2o/h2o-3项目的cpu管理的源码主要集中在h2o-core模块中,其中完全引入了jsr166y的源码(java7之后引入的支持Fork/Join框架多核并行计算库)。
以便在此基础上修改并扩展了MRTask和Job,从而支持带优先级的并行计算。关于原生jsr166y的Fork/Join框架此处不详细介绍,可以参考《Fork/Join框架介绍》和《JDK 7 中的 Fork/Join 模式》。h2o对jsr166y的扩展整体关系如下:
2.1线程(FJWThr)和线程池(PrioritizedForkJoinPool)
FJWThr扩展了jsr166y中的ForkJoinWorkerThread是一个带有优先级的线程:
PrioritizedForkJoinPool扩展了jsr166y中的ForkJoinPool,是一个带有优先级的线程池。
h2o中实例化了128个不同优先级的线程池
注:其中AckAck线程池具有最高优先级,该线程池用于处理远程RPC的响应,且在处理过程中不会被阻塞。
后面可以看到h2o中的作业(MRTask及其继承类)也是有优先级,因此作业提交的时候,就会被提交到对应优先级的线程池中等待调度。如此设计主要是为了实现以下两点:
(1) FJWThr不会执行比自己优先级低的任务
(2) 在Fork/Join框架的工作窃取(work-stealing)算法的基础上扩展支持优先窃取高优先级的作业
注:ForkJoinPool中维护了双向队列来支持work-stealing
2.2 作业(MRTask)
MRTask抽象类和jsr166y中的fork/join框架的继承关系如下图:
在Fork/Join框架中有一个ForkJoinTask抽象类。ForkJoinTask需要提交到ForkJoinPool中来调度执行,ForkJoinPool() 默认建立具有与 CPU 可使用线程数相等线程个数的线程池。ForkJoinTask通过fork和join两个操作,将一个任务拆分成多个“小任务”,把多个”小任务”放到多个处理器核心上并行执行。当多个“小任务”执行完成之后,再将这些执行结果合并起来即可
在h2o中作业按照优先级被提交到不同优先级的线程池中
ForkJoinPool最终会启动ForkJoinWorkerThread去执行ForkJoinTask的compute方法。h2o中的H2OCountedCompleter抽象类继承自ForkJoinTask并重写了compute方法,从而实现上面提到的优先抢占执行具有高优先级的作业:
可以看出这里的compute()方法主要是做了作业优先调度的功能,真正作业的执行内容是封装在H2OCountedCompleter实例的compute1()或compute2()方法中的。
此外:MRTask还实现了ManagedBlocked接口提供了block()和isReleasable()两个方法用于阻塞线程和判断阻塞是否可释放。h2o中利用这个接口实现了,当需要阻塞当前线程的时候,在当前线程池中判断是否需要新增一个线程,保证系统的并发性。(例如在MRTask的getResult()方法需要阻塞获取运行结果时使用)
2.3 分布式任务拆分执行
前面已经看到h2o内部通过封装jsr166y实现了支持cpu多核并发的带有优先级的任务调度。在此基础上h2o支持集群部署,因此实现了基于RPC的分布式任务拆分执行,这里先给出完整的调用示意图