HarmonyOS 7 FFRT 任务图、数据依赖与 QoS 调度机制【鸿蒙心迹】
明明创建了 8 个任务,为什么 FFRT 并没有简单地开 8 个线程同时执行?

做并发编程的时候,最开始的思路很简单:有 8 个任务,开 8 个线程,不就跑完了?
结果真用了 FFRT 之后发现不对:提交了 8 个任务,运行时并没有真的开 8 个线程同时跑。有的任务要等,有的任务并行跑,调度完全不是你想的那样。
这时候才意识到:FFRT 不是普通的线程池。它的核心不是"开多少线程",而是"把程序描述成一张任务图,然后自动调度"。
一、先写四个任务,看看它们到底怎么跑
先写四个任务,A、B、C、D,每个都打印一下开始和结束时间。
#include <ffrt/ffrt.hpp>
int main() {
ffrt::submit([]{
printf("A start\n");
sleep(1);
printf("A end\n");
});
ffrt::submit([]{
printf("B start\n");
sleep(1);
printf("B end\n");
});
ffrt::submit([]{
printf("C start\n");
sleep(1);
printf("C end\n");
});
ffrt::submit([]{
printf("D start\n");
sleep(1);
printf("D end\n");
});
}
跑一下,结果是什么?A、B、C、D 真的同时开始吗?
不一定。FFRT 会根据 CPU 核心数和系统负载来调度。比如 4 核 CPU,可能真的同时跑 4 个。但如果系统还有别的进程在跑,可能就只跑 2 个,剩下的排队。
二、任务依赖是什么意思
现在加个需求:B 必须等 A 跑完才能开始。
怎么实现?用 in_deps 和 out_deps。
| 概念 | 作用 |
|---|---|
| in_deps | 这个任务要等哪些任务完成才能开始 |
| out_deps | 这个任务完成后,哪些任务可以开始 |
依赖是通过数据地址建立的。不是用任务 ID,是用你要读写的数据的地址。
这段代码解决什么问题: 建立任务依赖关系。
文件: ffrt/task_demo.cpp
用途: 任务图调度
接入位置: Native 并发任务
int data = 0;
// A 任务:写 data
ffrt::submit({
.deps = {.out_deps = {&data}}
}, []{
data = 42;
});
// B 任务:读 data,要等 A 写完
ffrt::submit({
.deps = {.in_deps = {&data}}
}, []{
printf("data = %d\n", data);
});
这里 A 的 out_deps 是 &data,B 的 in_deps 也是 &data。FFRT 看到它们依赖同一个地址,就知道 B 要等 A 跑完。

三、任务图是怎么形成的
把多个任务和依赖放在一起,就形成了一张任务图。
比如:A 和 B 互相不依赖,C 要等 A,D 要等 B,E 要等 C 和 D。
A ──> C ──┐
├──> E
B ──> D ──┘
FFRT 会自动分析这张图:A 和 B 没有依赖,可以并行跑。C 要等 A,D 要等 B。E 要等 C 和 D 都完成。
这就是任务图的核心:不是你决定先跑谁后跑谁,是依赖关系决定的。
四、QoS 和任务优先级是什么
任务不只是有依赖,还有优先级。QoS 就是服务等级。
| QoS 等级 | 适合的任务 |
|---|---|
| 高优先级 | UI 响应、交互相关 |
| 中优先级 | 普通业务计算 |
| 低优先级 | 后台任务、预加载 |
QoS 高的任务,会优先被调度。不是说低优先级的不跑,是高优先级的先跑。
你还可以在任务运行的时候动态调整 QoS。比如一个任务开始是后台的,后来用户切换到前台了,就把它的 QoS 调高。
五、最大并发度是什么意思
FFRT 有个参数叫 max_concurrency,就是最大同时跑多少个任务。
为什么要限制?因为开太多线程反而慢。线程切换有开销,CPU 就那么多核心,开太多线程,大部分时间都在切换,反而做不了事。
| 并发度 | 效果 |
|---|---|
| 太小 | 任务排队,跑不完 |
| 合适 | 刚好跑满 CPU |
| 太大 | 线程切换开销大,反而慢 |
所以不是并发度越大越好。合适才重要。

六、ffrt_wait 是什么意思
提交了任务,怎么等它完成?用 ffrt_wait。
// 提交任务,拿到 handle
auto handle = ffrt::submit({
.deps = {.out_deps = {&data}}
}, []{
data = 42;
});
// 等这个任务完成
ffrt::wait(handle);
wait 会阻塞当前线程,直到任务完成。但注意:不要在任务内部 wait 另一个任务,这样会造成调度效率下降,甚至死锁。
七、几个容易踩的坑
第一个坑:把 FFRT 当成普通线程池。它不是,它是任务图调度。
第二个坑:随便拿 NULL 或固定数字当依赖标识。依赖是数据地址,不是随便填的。
第三个坑:两个任务实际访问同一份数据却没有建立依赖。两个任务同时写同一块内存,就出问题了。
第四个坑:依赖范围过大导致本来能并行的任务全部串行。in_deps 填太多,本来能并行的都变成串行。
第五个坑:盲目提高 QoS。所有任务都设最高优先级,等于没有优先级。
第六个坑:任务内部再次阻塞等待造成调度效率下降。任务里 wait 另一个任务,调度器就少了一个 worker。
第七个坑:把任务数量等同于线程数量。提交 100 个任务,不代表开 100 个线程。

这次做 FFRT 最大的体会是:FFRT 的核心不是线程池,是任务图。你不是在管理线程,你是在描述任务之间的依赖关系,调度器自动帮你决定怎么跑。
真正做的时候,最容易忽略的不是 API 怎么调,而是依赖怎么设计。依赖设计对了,任务自动并行。依赖设计错了,本来能并行的全部串行,性能上不去。
更多推荐




所有评论(0)