logo
积分中心
FFRT阻塞导致冻屏的分析链路复盘
头像 鸿蒙小助手 2026-10-21 15:36:35    发布
17 浏览 3 点赞 2 收藏

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

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 个 workerX 代表 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 响应性能。应将批量提交逻辑移至工作线程异步执行。


----------------------------------------------------------------------------------------------------------

🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5?ha_source=zxqy-IT&ha_sourceId=89000468

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

【扫码加入 HarmonyOS DFX 技术交流群】

©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255