eBPF 可观测摄像头带你 5 分钟真正看懂 ForkJoin 是如何“分而治之”?有什么隐藏的坑?
ForkJoin 是一种专为 CPU 密集型任务而生的线程池(比如计算 1~100 亿的和),它能充分利用 CPU 资源,把大任务拆分成众多小的子任务,多线程并行。但是你知道:
子任务的计算量拆分到多少才算合理吗?为什么你用了 ForkJoin 反而降低性能?
大量线程并行,如何规避线程阻塞?
虽知“先 fork 再 join”,但谁负责 join?有什么坑需要注意?
ForkJoinPool 的 invoke 和 submit 启动方式竟然还有隐藏的坑你知道吗?
本期眼见为实系列,我们将借助 Kindling 程序摄像头,查看一次请求计算下所有 ForkJoin 线程的工作情况记录和分析,来探究上述问题的答案。
实验 1:分析 ForkJoin 线程工作
首先我基于 ForkJoin 写了一个实验 demo:计算 1~7 的和。下图就是我用程序摄像头捕捉的一次请求计算的 trace 分析。不熟悉 kindling 程序摄像头操作的同学可以参考我们的操作手册:kindling程序摄像头操作手册
页面概览
线程轴放大
(上图中 futex 是指线程在等待,我让线程执行计算后都 sleep 了一段时间,便于放大线程轴观察) 通过每个线程的日志,我们分析发现,ForkJoinPool-worker-1 线程最先拿到了 fork1~7 任务,把它拆解成子任务 1(计算 1~4),子任务 2(计算 5~7),因为我设置的 ForkJoin 的最小任务计算数是 3,所以子任务 1 还需要 fork,而子任务 2 可以直接计算了。最终拆解结果如下图:
我们可以看到,worker-2 线程拆解出来的 1~2 的子任务又丢给了 worker-1 线程计算,此外,最后是由 worker-2 join 了 1~4 的结果,worker-1 join 了 1~7 的结果,即谁 fork 的任务,就由谁来 join 任务。 通过这个实验我们还可以看出,当某个线程执行时间长,需要 join 它结果的线程就会一直等待。如下图红框所示,我让 worker-2join 完 1~4 结果之后 sleep 了 30ms,然后 worker-1 线程这段时间被锁住了,通过点击黄色 lock 区块,我们可以看到堆栈信息,很明显,它在做等待,直到 worker-2 sleep 完成。
这也是 ForkJoin 为什么适合 CPU 密集型计算而不适合 IO 密集型,因为磁盘 IO、网络 IO 的操作特点就是等待,容易造成线程阻塞。另外,我们子任务的拆分要合理,避免造成任务阻塞。那怎么拆分子任务才算合理呢?像我这个实验 demo 的拆解其实是不合理的,需要考虑创建子任务、线程调度等操作都需要消耗时间和内存。官网文档给出的经验是:最小子任务需要计算 100~10000 个基本计算步骤,但是这个相对的最优解应该结合大家的实际业务需求,实践得出。大家就可以借助 Kindling 程序摄像头来做,在设置不同子任务粒度之后,可以观察计算的响应时间,和每个线程的工作实况、耗时分析。
实验 2:ForkJoinPool 的 submit 和 invoke 启动方式
invoke(ForkJoinTask),submit(ForkJoinTask)都有返回值,invoke 的 tasks 会被同步到主线程,但是 submit 是异步执行,需要通过 task.get 实现同步到主线程,什么意思呢?我们做了一个实验,下面是实验 demo:
请求开始打了日志,然后分别用 invoke、submit 启动 ForkJoinPool 测试,请求结束的地方也打了日志,然后 sleep 了 500ms(是为了放大线程轴,一会让大家看得更清楚)。下图是两种情况的测试结果。
submit 启动
invoke 启动
我们可以看到用 submit 启动线程池,主线程和 ForkJoin 线程是异步执行,而 invoke 启动时,主线程同步等待 ForkJoin 线程执行。在等待的时候,主线程被 lock 住(即上图中 4 个问号的黄色高亮区块),点击查看堆栈,确认它确实在等待。
通过这个实验发现,我们在使用 forkJoin 的时候一定要注意 pool 的启动方式,不然就会有隐藏的坑,比如下面这个场景:
先用 forkJoin 给所有订单计算利润,存到数据库订单表里,然后基于订单利润再计算每个销售的订单提成,存到提成表里。
如果开发同学不注意,计算提成的时候取的是订单表的利润,然后用的 submit 启动方式,那可能会造成,开始计算提成的时候,利润还没算好。以上就是本次关于 ForkJoin 分享的内容,你还想通过 Kindling 摄像头观测到什么场景的知识?微服务 RPC 调用机制?Java 锁?Java 分布式?线程安全?线程池调优?Redis 集群机制?欢迎添加微信好友告诉小编~
版权声明: 本文为 InfoQ 作者【KINDLING】的原创文章。
原文链接:【http://xie.infoq.cn/article/4c56f9c88b394164231de9c53】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论