鸿蒙小助手 2026-10-21 15:36:35 发布本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:
FFRT阻塞导致冻屏的分析链路复盘-华为开发者话题 | 华为开发者联盟
前置知识:如何识别 FFRT 线程
在分析 FFRT 相关问题之前,首先需要准确判断当前线程是否为 FFRT 线程。
关键原则:不能仅凭线程名判断线程是否为 FFRT 线程。
一个常见的误区是:开发者在 FFRT 线程内部又通过 pthread_create或 std::thread 等方式创建了新线程,此时新线程会继承父线程(即 FFRT 线程)的线程名。这种情况下,线程名虽然显示为 FFRT 线程名,但该线程实际上并不受 FFRT 调度器管理,也不属于 FFRT 线程池。
正确的判断方法:查看调用栈最底层(栈底)是否包含 libffrt.so。
FFRT 线程的入口函数由 libffrt.so 提供,因此其调用栈的最底层帧必然位于 libffrt.so 内。如果栈底没有 libffrt.so,即使线程名带有 FFRT 字样,该线程也不是 FFRT 线程。
前置知识:FFRT 任务提交的同步性
FFRT 的任务提交(submit)本身是一个同步调用。 虽然 FFRT 整体被称为"异步框架",但其"异步"仅体现在提交与执行之间的异步——任务被提交后,其执行由调度器在 worker 线程上异步完成。而提交动作本身是同步的:调用 submit 接口的线程会在提交流程中同步执行依赖关系建立、任务入队、限流检查等操作。因此,在提交流程中如果触发水线限流,调用提交接口的线程会被卡住睡眠,而非异步返回。
前置知识:QoS 共享特性
QoS(Quality of Service) 是 FFRT 的任务优先级分类机制,不同 QoS 等级的任务被分配到不同的调度优先级。FFRT 的 worker 线程池规模取决于是否开启了 QoS 共享特性,可通过 worker 线程名判断:
| QoS 共享特性 | 线程名格式 | Worker 线程上限 | 说明 |
| 已开启 | OS_FFRT_X | 全部 QoS 共享最多 64 个 worker | 所有 QoS 等级共享同一个线程池 |
| 未开启 | OS_FFRT_X_Y | 每个 QoS 独立最多 16 个 worker | X 代表 QoS 等级,Y 代表该等级下的 worker 编号 |
注意:QoS 共享特性仅在部分机型上开启,并非全局默认行为。在分析 worker 线程池容量时,需先通过线程名格式判断当前机型是否开启了该特性,再据此估算可用的 worker 线程数量。
案例一:主线程调用 FFRT wait 接口等待任务导致 Freeze
问题概述
当前 Freeze 由主线程(UI 线程)卡死引起。主线程调用了 FFRT 的 wait 接口(ffrt_wait_deps或ffrt::wait),同步等待某个 FFRT 任务完成。但该任务迟迟无法执行完毕,导致主线程长时间阻塞,最终触发应用冻屏(AppFreeze)。
问题现象
主线程 Freeze 在 wait 调用链上,典型调用栈如下:
#00 pc 00000000001df43c /system/lib/ld-musl-aarch64.so.1(__timedwait_cp+156)
#01 pc 00000000001e1590 /system/lib/ld-musl-aarch64.so.1(pthread_cond_timedwait+212)
#02 pc 00000000000c4910 /system/lib64/chipset-sdk-sp/libc++.so(std::__h::condition_variable::wait(...)+32)
#03 pc 00000000000a3ed8 /system/lib64/ndk/libffrt.so(ffrt::HDependenceManager::WaitDepsToHW(...)+400)
#04 pc 00000000000a3ae0 /system/lib64/ndk/libffrt.so(ffrt::HDependenceManager::onWait(ffrt_deps_t const*)+156)栈特征分析:
• 栈顶(#00~#02)为 libc/libc++ 的条件变量等待,说明线程处于阻塞睡眠状态。
• #03~#04 为 libffrt.so 内部的依赖管理逻辑(HDependenceManager::WaitDepsToHW / onWait),表明 FFRT 正在等待依赖任务完成。
问题代码示例
// 业务代码在主线程同步等待 FFRT 任务完成
void OnButtonClick() {
auto h = ffrt::submit_h([](){
// 可能阻塞的任务,比如下载/上传/IO等
});
ffrt::wait(h); // 主线程阻塞等待任务完成,任务耗时过长将导致 Freeze
}分析思路
第一步:解析符号,确定等待的具体任务
通过调用方的 so 解析调用栈帧对应的源码位置,确认当前正在等待哪个任务(依赖关系中的目标 task)。
第二步:根据任务状态分别排查
情形 A:目标任务处于"正在运行"(Running)状态
• 任务本身的栈帧信息,存在于Freeze文件中对应进程的栈信息中。
• 说明任务已被调度执行,但执行过程本身耗时过长或卡住。
• 直接定位到对应任务的代码,排查执行耗时原因(如弱网环境下网络请求超时、整机低内存导致频繁 GC、IO 等待等)。
情形 B:目标任务处于"协程切出"(Yield/Suspended)状态
• 协程切出是指 FFRT 任务在执行过程中,因等待依赖、锁或其他同步原语而主动让出当前 worker 线程的执行权,任务被挂起但尚未完成。
• 任务本身的栈帧信息,不存在于Freeze文件中对应进程的栈信息中。。
• 在 Freeze 文件中搜索关键词 blocked by synchronization primitive(mutex etc) or wait dependence,确认任务是否被同步原语(如 ffrt::mutex)阻塞。
修复方法
任务处于 Running 状态:
排查任务执行耗时过长的根因。常见场景包括:
• 弱网环境:网络请求超时时间设置不合理,需增加超时处理和降级逻辑。
• 整机低内存:频繁 GC 或内存回收导致执行变慢,需优化内存使用。
• IO 瓶颈:大文件读写或频繁 IO 操作,需异步化或缓存优化。
• std 同步原语调用:FFRT 闭包调用了std的同步原语,卡住协程调度,需替换对应的ffrt同步原语。
任务处于协程切出状态:
分析代码中的持锁与等锁逻辑,排查死锁或长时间持锁问题:
• 确认任务是否在 FFRT 闭包内使用了 ffrt::mutex、ffrt::condition_variable 等ffrt同步原语。
• 查找持锁方,确认持锁任务为何长时间不释放锁(是否同样被阻塞、是否存在循环等待)。
• 若存在死锁,重构依赖关系或锁的获取顺序。
建议与总结
1. 避免在 UI 线程同步等待耗时任务。 主线程调用 ffrt_wait_deps 会完全阻塞 UI 线程,应优先使用异步回调机制替代同步等待。
2. 控制 FFRT 线程池中的任务粒度。 避免向 FFRT 提交大量可能长时间运行的任务,防止单个任务长时间占用 worker 线程,影响其他任务的调度。
3. 在 FFRT 闭包中优先使用 FFRT 同步原语。 FFRT 同步原语(如 ffrt_mutex、ffrt_cond)在阻塞时会自动 yield 当前 worker 线程,让出执行权给其他任务;而 std 同步原语在阻塞时会持续占用 worker 线程,造成线程资源浪费。
案例二:任务提交速率超过消费速率导致 Freeze
问题概述
当前 Freeze 由主线程卡死引起。业务进程在短时间内向 FFRT 提交了大量任务,任务提交速率远大于 FFRT worker 线程的消费速率,导致待处理任务队列持续堆积。当待处理任务数量超过 FFRT 的水线阈值时,FFRT 的任务提交接口会触发限流机制,使调用提交接口的线程(主线程)进入睡眠等待,从而导致主线程卡死。
水线机制说明
水线 是 FFRT 的任务流量控制机制。当 FFRT 内部待处理任务队列中的任务数量超过预设的水线阈值时,submit 提交接口内部会调用 CheckTaskWaterLine 等接口进行限流检查,使提交者线程短暂 sleep,以防止任务无限堆积耗尽系统资源。当 worker 线程逐步消费任务、队列长度回落到水线以下时,提交者线程才会被唤醒继续提交。
在正常场景下,水线限流仅造成短暂的提交延迟;但当任务消费速率长期低于提交速率(如 worker 线程全部被阻塞),提交者线程将长时间处于 sleep 状态,最终导致主线程 Freeze。
问题现象
主线程 Freeze 在水线检查逻辑上,典型调用栈有两种形态:
栈形态一(限流逻辑在 libffrt.so 内):
#00 pc 00000000001e6644 /system/lib/ld-musl-aarch64.so.1(nanosleep+60)
#01 pc 00000000000d57e4 /system/lib64/chipset-sdk-sp/libc++.so(std::__h::this_thread::sleep_for(...)+100)
#02 pc 00000000000a7e80 /system/lib64/ndk/libffrt.so(ffrt::CheckTaskWaterLine()+188)
#03 pc 00000000000a87d0 /system/lib64/ndk/libffrt.so(ffrt::HDependenceManager::onSubmit(...)+876)
#04 pc 0000000000035700 /system/lib64/ndk/libffrt.so(ffrt_submit_base+172)栈形态二(限流逻辑在 libffrt_acc.so 内):
#00 pc 00000000001f17c4 /system/lib/ld-musl-aarch64.so.1(nanosleep+56)
#01 pc 00000000000d2c9c /system/lib64/chipset-sdk-sp/libc++.so(std::__h::this_thread::sleep_for(...)+112)
#02 pc 000000000000adc8 /vendor/lib64/passthrough/libffrt_acc.so(ffrt::FftsPlatform::SubmitToST64BV0(...)+340)
#03 pc 0000000000009784 /vendor/lib64/passthrough/libffrt_acc.so(int ffrt::FftsPlatform::SubmitToFftsImpl<(ffts::CmdType)0, false>(...)+76)
#04 pc 0000000000009550 /vendor/lib64/passthrough/libffrt_acc.so(void DrvSubmitCpuTaskToFfts<(ffts::CmdType)0>(...)+120)
#05 pc 00000000000a4808 /system/lib64/ndk/libffrt.so(ffrt::HDependenceManager::onSubmit(...)+948)
#06 pc 0000000000032324 /system/lib64/ndk/libffrt.so(ffrt_submit_base+128)栈特征分析:
• 两种形态的栈顶(#00~#01)均为 nanosleep / sleep_for,说明线程正处于睡眠限流状态。
• 栈形态一的限流逻辑直接在 libffrt.so 的 CheckTaskWaterLine 中完成。
• 栈形态二的限流逻辑经由 libffrt_acc.so 的 FftsPlatform::SubmitToST64BV0 完成。
• 两种形态的底层均由 ffrt_submit_base → HDependenceManager::onSubmit 触发,说明主线程正在执行任务提交操作时被限流。
问题代码示例
// 业务代码在主线程批量提交大量任务
void OnDataReceived(std::vector<DataItem>& items) {
for (auto& item : items) {
ffrt::submit([&item]() {
ProcessItem(item); // 任务执行可能耗时
}, nullptr, nullptr, nullptr);
}
// 大量任务堆积超过水线,主线程在 submit 内部被 sleep 限流
}分析思路
第一步:确认 QoS 共享特性的开启状态
根据前置知识中介绍的 QoS 共享特性,通过 worker 线程名格式(OS_FFRT_X 或 OS_FFRT_X_Y)判断当前机型是否开启了该特性,从而确定 worker 线程池的上限规模。
第二步:判断 worker 线程是否全部被阻塞
情形 A:所有 worker 线程均被卡死
• 检查各 worker 线程的调用栈,大概率发现它们卡在 std 同步原语(如 std::mutex、std::condition_variable)上。
• 由于 worker 线程被同步原语阻塞后无法 yield,FFRT 线程池实际上已丧失消费能力,任务队列持续堆积触发水线限流。
情形 B:部分 worker 线程未被卡死
• 存在 idle 状态的 worker(栈底在 libffrt.so 但栈顶为 idle 状态),或运行中的 worker 数量未达到上限。
• 此时仍出现水线限流,说明任务消费速率虽然不为零,但仍低于提交速率。需进一步分析被切出的任务(协程 yield 的任务),重点关注是否存在大量任务依赖被切出的任务,从而导致卡死。
修复方法
Worker 线程被卡死(全部阻塞):
1. 切换同步原语: 将 FFRT 闭包内使用的 std::mutex、std::condition_variable 等标准库同步原语替换为 FFRT 同步原语(ffrt::mutex、ffrt::condition_variable 等)。FFRT 同步原语在阻塞时会自动 yield 当前 worker 线程,释放线程资源给其他任务使用,避免 worker 线程被白白占用。
2. 排查死锁: 若 worker 线程之间形成循环等待(如多个任务互相持锁),需重构锁的获取顺序或使用无锁设计消除死锁条件。
Worker 线程未被全部卡死:
1. 分析持锁方: 在 Freeze 文件中搜索关键词 blocked by synchronization primitive(mutex etc) or wait dependence,确认任务是否被同步原语(如 ffrt::mutex)阻塞,查找被切出任务等待的锁,定位持锁任务,分析持锁时间过长的原因。
2. 优化任务粒度: 减少单个任务的执行时间,避免长任务独占 worker 线程。
3. 控制提交速率: 在业务侧增加批量提交的批次控制或流控机制,避免短时间内提交过多任务。
建议与总结
1. 在 FFRT 闭包中避免使用 std 同步原语。 std::mutex 等标准库同步原语在阻塞时会持续占用 worker 线程,导致线程池有效容量降低。应使用 FFRT 提供的同步原语(ffrt::mutex、ffrt::condition_variable等),这些原语在阻塞时会自动 yield,让出 worker 线程给其他任务执行。
2. 合理设计任务依赖与同步关系,避免死锁。 使用 FFRT 的依赖机制(ffrt_deps_t)声明任务间依赖关系,由调度器自动管理执行顺序,而非手动通过锁来同步,可有效降低死锁风险。
3. 控制任务提交速率,避免瞬时大量提交。 对于批量数据处理等场景,应分批次提交任务或使用 FFRT 的任务队列管理机制,使提交速率与消费速率保持匹配。
4. 避免在主线程提交大量任务。 主线程提交大量任务不仅可能触发水线限流导致 Freeze,还会影响 UI 响应性能。应将批量提交逻辑移至工作线程异步执行。
----------------------------------------------------------------------------------------------------------

🔗 社区DFX专题文章: https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001?ha_source=zxqy-IT&ha_sourceId=89000468


【扫码加入 HarmonyOS DFX 技术交流群】
相关推荐
鸿蒙小助手
22
0
鸿蒙小助手
17
0
鸿蒙小助手
30
0
鸿蒙小助手
17
0
鸿蒙小助手
35
0
鸿蒙小助手
致力于为鸿蒙开发者谋福利
帖子
提问
粉丝
ArkRuntime Freeze问题的多维度排查与分析路径
2026-10-21 14:53:28 发布RN 性能调优之基础定界与 trace 分析介绍
2026-10-21 14:51:08 发布



京公网安备:11010502051901号