编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 性能优化实践

    • README
    • 公共方法论

    • 体系建设篇

    • 资源专项篇

      • CPU监控与分析
      • 内存监控与治理
      • OOM与低内存治理
      • 线程模型调度优化
        • 01.阅读说明
        • 02.贯穿案例
          • 2.1 案例背景
          • 2.2 经验派的 3 周折腾(典型反面教材)
          • 2.3 方法派的 7 天闭环
          • 2.4 上线效果
          • 2.5 案例如何串起本文
        • 03.线程物理本质
          • 3.1 一句话定义
          • 3.2 现象与代价
          • 3.3 度量准则与基准
          • 3.4 反直觉问题清单
        • 04.并发模型原理
          • 4.1 四种并发模型分类
          • 4.2 线程问题的三类典型形态
          • 4.3 跨平台同构原理
          • 4.4 平台差异点矩阵
        • 05.度量与采集
          • 5.1 三类采集方案
          • 5.2 各方案的可见盲区
          • 5.3 跨平台采集对照表
          • 5.4 数据可信度评估
        • 06.归因决策树
          • 6.1 线程问题决策树
          • 6.2 死锁与竞争归因
          • 6.3 调度延迟归因
          • 6.4 线程数膨胀归因
        • 07.主线程全链路
          • 7.1 Android UI Thread 全链路
          • 7.2 iOS Main Thread 全链路
          • 7.3 Web Main JS Thread 全链路
          • 7.4 主线程负载分类
          • 7.5 主线程优化路径
        • 08.线程池全链路
          • 8.1 Android ExecutorService 全链路
          • 8.2 iOS GCD 全链路
          • 8.3 Web Worker 全链路
          • 8.4 线程池配置黄金法则
          • 8.5 跨端线程池对照
        • 09.同步原语全链路
          • 9.1 mutex 全链路
          • 9.2 spinlock 全链路
          • 9.3 读写锁 / CAS / 原子操作
          • 9.4 信号量与条件变量
          • 9.5 死锁四要素与破解
        • 10.优先级调度全链路
          • 10.1 Linux CFS 调度全链路
          • 10.2 Android 优先级管理
          • 10.3 iOS QoS 全链路
          • 10.4 优先级反转与继承
          • 10.5 关键路径的 QoS 策略
        • 11.协程全链路
          • 11.1 Kotlin 协程全链路
          • 11.2 Swift async-await 全链路
          • 11.3 Go goroutine 全链路
          • 11.4 协程 vs 线程对照
          • 11.5 协程的边界
        • 12.跨端对照
          • 12.1 五个全链路总览
          • 12.2 各平台优化优先级
          • 12.3 反直觉问题答疑
        • 13.治理一层主线程
          • 13.1 StrictMode 拦截
          • 13.2 长任务切片 + IdleHandler
          • 13.3 协程 / async-await 简化异步
          • 13.4 Web 长任务切片
          • 13.5 主线程减负检查清单
        • 14.治理二层同步
          • 14.1 锁粒度拆分(按维度分锁)
          • 14.2 锁内严禁 IO/网络/Binder
          • 14.3 ReadWriteLock / CopyOnWrite
          • 14.4 CAS / 原子操作消灭锁
          • 14.5 全局锁顺序避免死锁
        • 15.治理三层线程池
          • 15.1 集中线程池服务
          • 15.2 CPU 池 = availableProcessors() + 1
          • 15.3 IO 池容量按峰值估算
          • 15.4 HandlerThread / 线程显式回收
          • 15.5 SDK 强制接入
        • 16.治理四层调度
          • 16.1 优先级继承锁
          • 16.2 关键路径线程升 QoS
          • 16.3 后台任务降 QoS + 限流
          • 16.4 大小核绑定(线程亲和性)
          • 16.5 ROI 排序
          • 16.6 避免反向收益
        • 17.求证实验 ⭐
          • 17.1 实验一:线程切换代价
          • 17.2 实验二:线程池拐点
          • 17.3 实验三:锁竞争代价
          • 17.4 实验四:优先级反转
          • 17.5 实验五:协程 vs 线程
          • 17.6 五大实验启示
        • 18.实战案例
          • 18.1 跨端同构案例:SDK 线程膨胀
          • 18.2 平台特异案例:iOS GCD 主队列死锁
          • 18.3 反例案例:CachedThreadPool 引发线程爆炸
        • 19.防劣化体系
          • 19.1 三道防线总览
          • 19.2 编码期 Lint
          • 19.3 CI 卡口
          • 19.4 线上 SLO
          • 19.5 文化建设
        • 20.跨平台速查
          • 20.1 工具速查
          • 20.2 关键 API 速查
          • 20.3 各平台优化清单
        • 21.总结与延伸
          • 21.1 五条核心原则
          • 21.2 五个常见误区
          • 21.3 一句话总结
          • 21.4 延伸阅读
      • 进程与多进程优化
      • IO与存储性能
    • 流水线专项

    • 业务专项篇

    • 交付防御篇

  • 程序编程原理

  • 稳定性与可靠性

  • 工程化与运维

  • 方案设计思想

  • 专栏
  • 性能优化实践
  • 资源专项篇
杨充
2026-05-27
目录

线程模型调度优化

# 线程模型与调度优化

本文核心命题:线程是"用 CPU 兑换延迟"的工具——通过把任务分散到多个 CPU 核 / 时间片来降低单次任务延迟。但线程不是免费的:创建、切换、同步都有代价。一切优化都是要找到"并发收益"和"切换 / 锁开销"之间的最优点。


# 01.阅读说明

  • 本文卷归属:卷二 · 资源篇 · 第 4 篇
  • 本文目标层级:L2 进阶 → L3 专家
  • 适用平台:Android(主) / iOS / Web / 跨端框架 / 嵌入式
  • 前置阅读:
    • 卷二·01 CPU 监控与分析(线程是 CPU 的使用者)
    • 卷三·03 卡顿捕获与归因(线程问题导致卡顿)

全文 21 章地图:

   §01 阅读说明           §02 贯穿案例           §03 线程物理本质      §04 并发模型原理
   §05 度量与采集         §06 归因决策树
   §07 主线程全链路 ⭐    §08 线程池全链路 ⭐    §09 同步原语全链路 ⭐
   §10 优先级调度全链路 ⭐  §11 协程全链路 ⭐    §12 跨端对照
   §13 治理一层主线程 ⭐   §14 治理二层同步 ⭐   §15 治理三层线程池 ⭐  §16 治理四层调度 ⭐
   §17 求证实验 ⭐         §18 实战案例           §19 防劣化体系          §20 跨平台速查
   §21 总结与延伸
1
2
3
4
5
6
7

阅读建议:先读 §02 案例 → §03/§04 拿到原理 → §05/§06 学会度量归因 → §07-§11 五个全链路(主线程/线程池/同步/调度/协程)→ §13-§16 四层治理 → §17 求证 → §18-§20 工程闭环。


# 02.贯穿案例

本案例贯穿全文:§03 看懂代价、§04 拿到并发模型武器、§05/§06 用三方案+决策树定位、§17 用实验复盘、§13-§16 给出分层策略闭环。

# 2.1 案例背景

某直播 App V9.2 上线"开播秒开"功能,把推流准备从异步改成全异步并行。结果出乎意料:

  • 开播 3 秒内卡死率从 0.5% 飙到 7.3%,被打"应用未响应"标签。
  • 主播侧首屏 P95 从 1.2s 升到 4.8s(与"秒开"目标相反)。
  • 中端机线程数稳态达 187 个(之前 65 个),内存涨 80MB。
  • 日活主播流失 12%,直播业务损失约单日 600 万。

研发组初步排查:"并行应该更快才对啊。"——这是典型的"并发崇拜"。

# 2.2 经验派的 3 周折腾(典型反面教材)

周次 动作 结果
第 1 周 把线程池从 8 调到 32(怀疑并发不够) 卡死率升到 9.1%:线程数破 230,上下文切换暴涨
第 2 周 加 try-catch + 死循环检测(怀疑死锁) 没死锁,但卡死依旧
第 3 周 用 spinlock 替代 mutex(怀疑锁开销) CPU 占用炸到 100%,发热严重,掉电飞快

复盘:三周里所有动作都基于"并发越多越快、锁越快越好"的错误直觉。线程不是免费的(§3.1 约束三),并发是非线性收益(§17.2 拐点)——这正是案例的根本反面教材。

# 2.3 方法派的 7 天闭环

新接手的同学按本文方法论重做:

Day 1(§04 第一性原理 + §05 三方案组合):用 Perfetto 抓系统 trace(方案②)+ Lock contention(方案③)+ 线程清单(方案①),看到三个关键数据:

指标 测量值 期望
线程数 187 < 60
上下文切换/s 22,000 < 5,000
推流锁等待 P99 850 ms < 5 ms

→ 三类问题(§04 模型)全部命中:调度不当 + 同步开销 + 资源浪费。

Day 2(§06 归因决策树):跟着决策树跑:

  • 线程数 187 → 线程数膨胀分支 → 12 个 SDK 各自创建线程池
  • 切换 22K/s → 上下文切换过频分支 → 每帧 30+ 任务被切散到不同线程
  • 锁等待 850ms → 锁竞争高分支 → "推流准备锁"被音频/视频/字幕/特效 4 路同时争用

Day 3-4(§13-§16 分层策略):

  • 第 1 层(主线程减负):开播状态机改用 IdleHandler 切片
  • 第 2 层(同步治理):推流准备锁拆分为 4 把独立锁;锁内严禁 IO
  • 第 3 层(线程池治理):建统一线程池服务(CPU 池=核数+1 / IO 池=20 / 高优先级池=4)
  • 第 4 层(优先级调度):推流相关线程升 QoS 到 userInteractive;锁启用优先级继承

Day 5(§17 实验思路验证):用 Macrobenchmark 跑前后对比,确认主线程等待 P99 从 850ms→18ms。

Day 6-7(上线灰度):

# 2.4 上线效果

指标 经验派 3 周后 方法派 7 天后
开播 3 秒卡死率 9.1% 0.3%
主播首屏 P95 4.8s 1.0s
线程数稳态 230 52
上下文切换/s 35,000 4,200
推流锁等待 P99 850 ms 18 ms
日活主播流失 -12% +3%(恢复)

核心洞察:经验派 3 周折腾错在"用更多线程治线程问题"——加线程让切换雪上加霜(22K/s→35K/s),spinlock 把锁等待换成 CPU 烧死。线程优化的本质是找拐点(§17.2),不是堆资源。

# 2.5 案例如何串起本文

  • §03 现象与代价 ▶▶ 业务损失映射:日活主播 -12%、单日 600 万收入。
  • §04 并发模型 ▶▶ 案例同时命中"调度不当 + 同步开销 + 资源浪费"三类。
  • §07-§11 五大全链路 ▶▶ 主线程/线程池/同步/调度/协程 五条链路对应案例每一类问题。
  • §17 求证实验 ▶▶ §17.2 拐点、§17.3 锁竞争、§17.4 优先级反转都在案例中变现。
  • §13-§16 四层治理 ▶▶ "主线程→同步→线程池→优先级"四层正是案例落地路径。

探索性思考:为什么"并发崇拜"是工程师最容易陷入的认知陷阱?因为"并发"是个好词——听起来"快"、"先进"、"现代"。但真实世界里,线程不是抽象概念,而是有物理代价的资源。每一个新线程都消耗栈内存(1MB)、TCB(10KB)、调度时间(μs 级)、缓存污染(10-100μs)。当你说"加一个线程"时,你其实在说"花 1MB+10KB+μs+污染换一个并发槽" —— 这个权衡被遗忘的瞬间,就是反向收益的开始。


# 03.线程物理本质

# 3.1 一句话定义

线程 = 操作系统调度的最小执行单元,是"在共享地址空间内独立执行指令流"的抽象。

这句话隐含三个不可商量的物理约束:

约束一:线程是 CPU 时间片的接收者

CPU 资源有限(N 个核心),线程数往往远多于核数。OS 通过"时间片轮转"让多个线程"看似并行"运行。单个核同时只能跑一个线程。

   单核 CPU 视角:
   时间 ──▶ |T1|T2|T1|T3|T2|T1|T2|T3|...
              ↑切换 ↑切换 ↑切换
              每次切换有成本(详见 §17.1)
1
2
3
4

约束二:线程共享地址空间

同进程内所有线程共享内存,意味着任意线程可以访问 / 修改其他线程的数据。这是线程的"双刃剑":

  • 优势:通信成本低(直接共享变量)。
  • 代价:必须用同步原语(锁、原子操作)保证数据一致性。

约束三:线程不是免费的

每个线程都有固定的资源占用:

  • 栈内存:默认 1MB(Linux)/ 8MB(macOS),即使啥都不干。
  • TCB:内核数据结构 ~10KB。
  • 调度开销:每次切换 ~1–10μs。
  • 缓存污染:切换后 L1/L2 缓存重新加载。

这就是为什么"开 1000 个线程"会带来巨大开销,即使它们大部分时间在 sleep。

# 3.2 现象与代价

线程问题往往表现间接,但代价巨大:

  • 主线程卡顿:耗时任务没异步化 → 帧时长超 16.67ms → 用户感知卡。
  • 线程膨胀 / OOM:大量临时线程 → Thread 资源耗尽(Linux 默认 ~32K 进程级线程)。
  • 死锁 / 活锁:多个线程互相等待,进程"卡死",最终 ANR / 看门狗复位。
  • 数据竞争 / 状态不一致:多线程读写共享变量,UI 显示错乱、崩溃。
  • 后台耗电 / 温升:过多后台线程消耗 CPU 时间片,电量加速下降。

业务代价(行业实测数据):

  • 头部 App:主线程优化(移走 IO/JSON 等任务)后启动 -300ms,留存 +0.5%。
  • iOS 死锁直接被系统 watchdog 杀死,体验类崩溃。
  • 大量后台线程会让设备温度上升 2–5°C,触发降频。

▶▶ 回扣 §02 案例:直播 App 在"开播秒开"功能后,线程数 65→187、卡死率 0.5%→7.3%——线程优化的反面教材就是加更多线程。

# 3.3 度量准则与基准

资源视角(USE):

指标 含义 阈值参考
线程数 进程内活跃线程数 < 50(轻量)/ < 100(重量)
上下文切换率(S) 每秒切换次数 < 5000/s
死锁错误(E) 是否检测到 = 0

请求视角(RED):

指标 含义 阈值参考
任务执行延迟 提交到完成时间 < 100ms(UI 类)
锁等待时长 线程等锁时长 < 5ms
主线程消息处理 单条消息耗时 < 16ms

行业基准:

平台 进程内线程数 线程池大小 上下文切换
Android(中端机) < 60 CPU 核数 + 1(计算) < 5000/s
iOS < 50 同上(GCD 自动管理) < 5000/s
Web < 10 Worker 视场景 N/A
嵌入式 RTOS 严格规划 静态分配 < 1000/s

# 3.4 反直觉问题清单

带着这些问题阅读:

  1. 开 16 个线程是不是比 4 个快 4 倍?
  2. Thread.sleep(1) 真的只睡 1ms 吗?
  3. CPU 利用率 100% 是好事还是坏事?
  4. synchronized / Lock 哪个开销更大?
  5. 主线程不能做 IO,但子线程做 IO 是不是就没影响了?
  6. iOS GCD 是"无限线程"吗?
  7. Web Worker 越多越快吗?
  8. 后台线程优先级低是不是就不会影响前台?

探索性思考:为什么"线程"作为抽象在工程界被滥用?因为它把复杂的物理(CPU 时间片调度)封装成简单的 API(Thread.start())。好的抽象让简单的事情简单,但坏的抽象让复杂的事情看起来简单。线程的 API 是后者——new Thread().start() 看起来像免费午餐,实际上你买单了 1MB 内存 + 10KB TCB + 调度成本。抽象的代价从来都不会消失,只是被隐藏了。


# 04.并发模型原理

# 4.1 四种并发模型分类

按"任务粒度 + 调度策略 + 同步方式"可以分为四种典型模型,所有平台的并发设计都映射到这四种:

   ┌─────────────────────────────────────────────────────┐
   │ A. 共享内存 + 同步原语(经典多线程)                  │
   │    pthread / Java Thread / iOS NSThread             │
   │    优点:粒度灵活                                    │
   │    缺点:锁难写,死锁易发                             │
   ├─────────────────────────────────────────────────────┤
   │ B. 任务队列 + 线程池(GCD / ExecutorService)         │
   │    把"线程数"和"任务数"解耦                          │
   │    优点:开发者只关心任务,OS 管线程                  │
   │    缺点:长任务阻塞池,需要分类(IO/CPU)              │
   ├─────────────────────────────────────────────────────┤
   │ C. 事件循环 + 单线程(Web Main Thread / Node.js)    │
   │    一个线程上跑事件队列,任务非阻塞                   │
   │    优点:无锁                                       │
   │    缺点:任何长任务卡死全部                          │
   ├─────────────────────────────────────────────────────┤
   │ D. Actor / CSP(Erlang / Go goroutine / Akka)       │
   │    任务通过消息通信,无共享内存                       │
   │    优点:避免数据竞争                                │
   │    缺点:消息复制有成本                              │
   └─────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4.2 线程问题的三类典型形态

   线程问题类型:

   ┌──────────────────────────────────────────────┐
   │ ① 调度不当:任务在错的线程上执行              │
   │    主线程做 IO;子线程更新 UI                 │
   ├──────────────────────────────────────────────┤
   │ ② 同步开销:锁 / 竞争 / 死锁                  │
   │    粒度过大;锁顺序不一致;忘 unlock          │
   ├──────────────────────────────────────────────┤
   │ ③ 资源浪费:线程过多 / 切换过频               │
   │    每个任务起新线程;线程池配置不当           │
   └──────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12

▶▶ 回扣 §02 案例:直播 App 的"开播秒开"翻车恰好同时命中三类——

  • ① 调度不当:开播状态机仍在主线程做同步任务;
  • ② 同步开销:4 路争抢同一把推流准备锁,P99=850ms;
  • ③ 资源浪费:12 个 SDK 各自池化导致 187 个线程、22K/s 切换。

经验派失败的根本原因是把三类混为一谈,只看症状不分类型。

# 4.3 跨平台同构原理

底层都是 POSIX thread(pthread)或其衍生。区别只是上层封装:

   通用线程模型:

      [应用层 API]
      Thread / GCD / Worker / goroutine
              │
              ▼
      [运行时层]
      JVM ThreadPool / NSOperationQueue / V8 / Go runtime
              │
              ▼
      [系统层]
      pthread_create / pthread_mutex / futex
              │
              ▼
      [内核]
      task_struct / scheduler / runqueue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

正因为它们都长这个样子,线程问题的本质也是同一个:

线程问题不是平台 bug,是"共享资源 + 抢占调度 + 同步必需"的必然产物。

跨平台术语对照

通用术语 Android iOS Web C/C++
线程 Thread / HandlerThread NSThread / GCD Worker pthread_t
线程池 ExecutorService / Executors DispatchQueue.global Worker pool 手写
互斥锁 synchronized / ReentrantLock NSLock / os_unfair_lock Atomics.wait pthread_mutex_t
主线程 UI Thread Main Thread Main JS Thread 第一个线程
后台优先级 Process.THREAD_PRIORITY_BACKGROUND DispatchQoS.background N/A setpriority

# 4.4 平台差异点矩阵

维度 Android iOS Web C/C++
主流模型 多线程 + Handler/Looper GCD + Operation Queue Main + Worker(单线程内事件循环) pthread
线程创建开销 ~100μs(Java Thread) ~50μs(GCD 内部) Worker ~10ms ~50μs
默认栈大小 1MB 512KB(次)/ 8MB(主) 1MB 1MB(Linux)
调度器 CFS(Linux 内核) GCD + XNU 调度 V8 Microtask + 浏览器进程调度 OS 调度
优先级 nice -20~19 QoS class 无(浏览器决定) nice

探索性思考:为什么"四种并发模型"是工程上的稳定划分?因为它们对应了"通信方式 × 调度策略"的四个象限。好的抽象划分通常对应物理本质 —— 这四种不是被发明的,而是被发现的:每个模型都是某种物理约束的最优解。理解这点的工程师选择并发模型时不再"凭感觉",而是先问"我的任务粒度 + 通信需求是什么"。


# 05.度量与采集

# 5.1 三类采集方案

   ① 静态快照采集(线程清单、栈大小)
   ② 动态系统追踪(系统级 trace + 切换次数)
   ③ 锁与竞争采集(lock contention 数据)
1
2
3

① 静态快照采集

// Android:扫 /proc/<pid>/task
fun listThreads(): List<ThreadInfo> {
    val pid = android.os.Process.myPid()
    return File("/proc/$pid/task").listFiles()?.map { dir ->
        ThreadInfo(
            tid = dir.name.toInt(),
            name = File(dir, "comm").readText().trim(),
            state = File(dir, "stat").readText().split(" ")[2]
        )
    } ?: emptyList()
}
1
2
3
4
5
6
7
8
9
10
11

优势:直接看线程数与名称。
局限:不能看切换频率与锁等待。

② 动态系统追踪

平台 工具
Android Perfetto / Systrace
iOS Instruments System Trace
Web Performance Panel
Linux perf / ftrace

优势:完整调度过程(运行/阻塞/等待)。
局限:开销中等;离线分析。

③ 锁竞争采集

平台 方法
Android Perfetto Lock Contention
iOS Instruments Threads
C/C++ perf lock

优势:直接看哪把锁是瓶颈。
局限:需要 root(部分场景)。

# 5.2 各方案的可见盲区

方案 钩子位置 数据粒度 性能开销 跨端通用性 线上可用 主要局限
① 静态快照 /proc/task 等 线程级 极低 多端有差异 是 不知切换
② 系统追踪 内核 trace 调度事件 中 跨端工具 部分 离线分析
③ 锁竞争 mutex/futex 锁级 中 部分平台 否(线下) 需调试模式

实战建议:① 线上常态;② + ③ 用于深度排查。

# 5.3 跨平台采集对照表

平台 静态快照 系统追踪 锁竞争
Android /proc/pid/task Perfetto Perfetto Lock
iOS sysctlbyname Instruments Instruments Threads
Web navigator.hardwareConcurrency Performance Panel (无原生)
Linux ps -L / top -H perf perf lock

# 5.4 数据可信度评估

  • 静态快照:可信度高,但只看"现在"。
  • 系统追踪:可信度最高,但开销影响数据。
  • 锁竞争:可信度高,但通常仅离线。

探索性思考:为什么"线程问题"经常被工程师后置发现?因为它的症状(卡顿/ANR)和根因(线程数/锁等)之间隔着多个抽象层。静态快照看不到动态、动态追踪看不到静态 —— 必须三种方案配合才能看清全貌。线程问题的复杂性 = 调度的复杂性 + 同步的复杂性 + 多核的复杂性,三者互相耦合。


# 06.归因决策树

# 6.1 线程问题决策树

   症状 = ?
      │
      ├─ 主线程卡顿(帧时长 > 16ms)
      │     │
      │     └─ 走"主线程任务异常"分支:
      │        - 检查主线程消息队列
      │        - 是否有 IO/网络/JSON 解析
      │        - 治理:§13 主线程减负
      │
      ├─ ANR / 死锁
      │     │
      │     └─ 走"死锁/锁竞争"分支:
      │        - 检查持锁链路
      │        - 锁内是否有 IO/网络
      │        - 治理:§14 同步治理
      │
      ├─ 线程数膨胀(> 100)
      │     │
      │     └─ 走"线程膨胀"分支:
      │        - 看哪些 SDK 创建了多少线程
      │        - 是否有 HandlerThread 未 quit
      │        - 治理:§15 线程池治理
      │
      └─ 偶发卡顿 / P99 长尾
            │
            └─ 走"调度问题"分支:
               - 检查优先级反转
               - 检查后台任务抢前台 CPU
               - 治理:§16 优先级调度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 6.2 死锁与竞争归因

死锁四要素:

  1. 互斥:资源不能共享
  2. 持有并等待:持有 A 时等 B
  3. 不可剥夺:必须自己释放
  4. 循环等待:A→B→C→A

破任一即可。最常用:破"循环等待"(全局锁顺序)。

# 6.3 调度延迟归因

典型调度延迟:

  • 等待 CPU 时间片:runqueue 长,线程数多
  • 被高优先级抢占:低优先级线程"被等"
  • 大小核切换:跨集群切换 50μs+
  • 缺页异常:内存换页

# 6.4 线程数膨胀归因

典型膨胀模式:

  • 每个 SDK 各自线程池(X SDK = X 池)
  • HandlerThread 未 quit
  • ExecutorService 未 shutdown
  • new Thread 不池化
  • coroutine GlobalScope 滥用

探索性思考:为什么"决策树"是线程归因的最佳工具?因为线程问题的症状是高度结构化的——主线程卡 / ANR / 膨胀 / 长尾,每种症状对应特定根因路径。结构化症状的领域适合用决策树 —— 不需要 ML,工程经验已足够。


# 07.主线程全链路

主线程是用户感知的入口,是所有 UI 平台的"必经之路"。

# 7.1 Android UI Thread 全链路

   ① ActivityThread.main() 创建主线程
      ↓ Looper.prepareMainLooper()
   ② Looper.loop() 进入消息循环
      ↓
   ③ MessageQueue.next() 取下一条消息
      ↓
   ④ msg.target.dispatchMessage(msg)
      - INPUT 事件 / VSYNC 帧回调 / 各种 post 的 Runnable
      ↓
   ⑤ 处理完毕 → goto ②
1
2
3
4
5
6
7
8
9
10

关键观察:

  • 主线程是单线程消息循环(C 模型)
  • 任何一条消息 > 16ms 必然掉帧
  • 任何一条消息 > 5s 触发 ANR

主线程禁止操作:同步 IO / 同步网络 / 长时间 CPU 计算 / 同步 Binder。

# 7.2 iOS Main Thread 全链路

   ① 应用启动 → main() → UIApplicationMain()
      ↓
   ② 主 RunLoop(NSRunLoop.mainRunLoop)启动
      ↓
   ③ 处理事件源:InputSource / Observer / Performer
      ↓
   ④ CADisplayLink 帧回调
      ↓
   ⑤ Core Animation transaction
1
2
3
4
5
6
7
8
9

iOS 关键约束:

  • UIKit API 必须主线程调用
  • Watchdog 监控(启动 20s / 操作 8s)
  • GCD 主队列 = 主线程

# 7.3 Web Main JS Thread 全链路

   ① 浏览器主线程跑 V8 Isolate
      ↓
   ② 事件循环:
      - Macrotask 队列(setTimeout / IO)
      - Microtask 队列(Promise.then)
      ↓
   ③ Render:Style / Layout / Paint / Composite
      ↓
   ④ requestAnimationFrame
      ↓
   ⑤ 回到 ②
1
2
3
4
5
6
7
8
9
10
11

Web 特殊性:

  • 所有 JS 代码默认在主线程
  • DOM 操作必须主线程
  • Web Worker 可以分担 CPU 任务(但不能直接操作 DOM)

# 7.4 主线程负载分类

   ┌─────────────────────────────────────┐
   │ 必要负载(无法移走):                │
   │   UI 渲染 / 输入分发 / 帧回调          │
   ├─────────────────────────────────────┤
   │ 应该移走的负载:                      │
   │   DB / 网络 / JSON / 计算 / Bitmap   │
   ├─────────────────────────────────────┤
   │ 误操作(必须修复):                  │
   │   同步 sleep / 同步等待 / 锁内长持有  │
   └─────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10

# 7.5 主线程优化路径

   ① StrictMode 拦截 → 强制约束
   ② 长任务切片 → 单条消息 < 8ms
   ③ IdleHandler 推迟 → 非紧急任务挪到空闲帧
   ④ 协程 / async-await → 简化异步
1
2
3
4

▶▶ 回扣 §02 案例:开播状态机改 IdleHandler 后主线程 P99 从 200ms 降到 8ms。

探索性思考:为什么"主线程"是所有 UI 框架的共同设计?因为 UI 状态需要串行一致性 —— 所有跨平台框架都没敢挑战这一点。主线程是工程上的"宇宙秩序"。


# 08.线程池全链路

# 8.1 Android ExecutorService 全链路

   ① Executors.newFixedThreadPool(4)
      ↓
   ② ThreadPoolExecutor 创建:
      - corePoolSize / maximumPoolSize
      - LinkedBlockingQueue
      - ThreadFactory
      ↓
   ③ submit(task):
      - 池内有空闲 → 直接跑
      - 没空闲但 < maxSize → 创建新线程
      - 满了 → 加入队列
      - 队列也满 → RejectedExecutionHandler
      ↓
   ④ 线程跑完一个任务 → 继续从队列拿
1
2
3
4
5
6
7
8
9
10
11
12
13
14

经典线程池类型:

类型 特点 适用
FixedThreadPool 固定大小 CPU bound
CachedThreadPool 弹性,60s 回收 IO bound(短任务)
SingleThreadExecutor 1 个线程 顺序执行
ScheduledThreadPool 定时调度 定时任务
ForkJoinPool work-stealing 计算密集(递归)

反模式:

  • newCachedThreadPool 在 IO 密集场景下可能创建上千线程
  • 建议用 ThreadPoolExecutor 直接构造,明确各参数

# 8.2 iOS GCD 全链路

   ① DispatchQueue.global(qos: .userInitiated)
      ↓
   ② GCD 内部维护多个线程池(按 QoS 分)
      ↓
   ③ async { task }:任务入队 / GCD 选线程跑
      ↓
   ④ 线程跑完 → 自动复用
1
2
3
4
5
6
7

GCD QoS 等级:

QoS 用途
userInteractive UI 相关
userInitiated 用户触发
default 默认
utility 长时间任务
background 后台清理

GCD 特殊性:

  • 自动管理线程数(系统决定)
  • 可能创建较多线程(线程爆炸警告)
  • 主队列 = 主线程

# 8.3 Web Worker 全链路

   ① new Worker('worker.js')
      ↓
   ② 浏览器创建独立 V8 Isolate(独立堆)
      ↓
   ③ postMessage(data):数据结构化克隆,跨 Worker 传递
      ↓
   ④ Worker 中 onmessage 处理
      ↓
   ⑤ Worker postMessage 回主线程
1
2
3
4
5
6
7
8
9

Web Worker 限制:

  • 无 DOM 访问
  • 数据通过 postMessage 传递(拷贝开销)
  • 创建开销 ~10ms(远比线程慢)

# 8.4 线程池配置黄金法则

CPU bound:

   线程数 = CPU 核数 + 1
1

IO bound:

   线程数 = CPU 核数 × (1 + 等待时间/计算时间)
   典型 = 20-50
1
2

混合:拆成两个池(CPU 池 + IO 池),不要"一个池跑所有"。

# 8.5 跨端线程池对照

平台 主流方案 线程数策略 队列
Android ExecutorService 显式配置 LinkedBlockingQueue
iOS GCD 系统自动 内部队列
Web Worker pool 手动管理 postMessage
Kotlin 协程 Dispatchers.IO 64 默认 ChannelQueue
Go goroutine pool 由 runtime 管 go channel

▶▶ 回扣 §02 案例:Day 3 集中线程池服务让线程数从 187 降到 52。集中管理是治膨胀的根本手段。

探索性思考:为什么 GCD 比 ExecutorService 更"优雅"?因为它把"线程数"从开发者手中拿走了。好的抽象隐藏不必要的细节。但代价是当系统决策错误时,开发者无能为力。抽象的代价 = 控制权的让渡。


# 09.同步原语全链路

# 9.1 mutex 全链路

   ① 线程 A 调用 mutex.lock()
      ↓
   ② CAS 尝试获取:
      - 成功(无竞争)→ 进入临界区,~50ns
      - 失败(有竞争)→ 进入慢路径
      ↓
   ③ 慢路径:陷入内核 futex_wait
      - 线程被挂起
      - 加入等待队列
      ↓
   ④ 持锁线程释放 → futex_wake
      - 唤醒等待线程
      ↓
   ⑤ 被唤醒线程获得锁 → 进入临界区
1
2
3
4
5
6
7
8
9
10
11
12
13
14

关键开销:

  • 无竞争 ~50ns
  • 有竞争 5-50μs(陷入内核)
  • 等待时间 = (N-1) × 临界区时长

# 9.2 spinlock 全链路

   ① spinlock.lock()
      ↓
   ② while (CAS 失败) { /* spin */ }
      - 不陷入内核
      - 持续消耗 CPU
      ↓
   ③ CAS 成功 → 进入临界区
1
2
3
4
5
6
7

spinlock 适用场景:

  • 临界区极短(< 1μs)
  • 无大量竞争
  • 多核 CPU

spinlock 反模式:

  • 临界区长 → CPU 烧
  • 单核 CPU → 死锁
  • 高竞争 → CPU 100%

▶▶ 回扣 §02 案例:第 3 周 spinlock 替代 mutex,CPU 占用炸到 100% 发热严重——正是 spinlock 反模式的真实代价。

# 9.3 读写锁 / CAS / 原子操作

ReadWriteLock:

  • 多读并发,单写独占
  • 适合读多写少(缓存类)
  • 写多场景反而比 mutex 慢

CAS / Atomic:

  • 单变量更新
  • 比 mutex 快 10-100×
  • 不适合复杂状态

LongAdder(Java 8+):

  • 高并发计数器
  • 比 AtomicLong 快 5-10×(高并发下)

# 9.4 信号量与条件变量

Semaphore:

  • 限制并发数(如最多 N 个任务)
  • 跨线程通知

Condition Variable:

  • 等待某条件成立
  • 配合 mutex 使用

# 9.5 死锁四要素与破解

死锁四要素:

  1. 互斥
  2. 持有并等待
  3. 不可剥夺
  4. 循环等待

破任一即可。最常用:破"循环等待"——全局锁顺序。

// 全局规定:所有锁按 hash 升序加
void transfer(Account a, Account b, int amount) {
    Account first = a.id < b.id ? a : b;
    Account second = a.id < b.id ? b : a;
    synchronized(first) {
        synchronized(second) { /* ... */ }
    }
}
1
2
3
4
5
6
7
8

探索性思考:为什么"锁"是工程上最难掌握的工具?因为它的复杂性是组合性的——单把锁简单,两把锁就有死锁,三把锁有活锁。锁的复杂度随锁数量呈指数级增长。这就是为什么"避免锁"(用消息传递、CAS、不可变数据)越来越主流——不是锁不好,而是它的复杂度天花板太低。


# 10.优先级调度全链路

# 10.1 Linux CFS 调度全链路

   ① 每个 task_struct 有 nice 值(-20~19)
      ↓
   ② CFS 维护红黑树(按 vruntime 排序)
      ↓
   ③ 调度器选 vruntime 最小的跑
      - vruntime = 实际运行时间 / nice 权重
      - nice 低 = 权重大 = vruntime 涨慢
      ↓
   ④ 时间片到 → 重新入队
1
2
3
4
5
6
7
8
9

关键:CFS 是"完全公平调度"——不是 strict priority,而是按权重分配。

# 10.2 Android 优先级管理

// 设置线程优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY)  // -4
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)  // 10

// 优先级取值
THREAD_PRIORITY_AUDIO        // -16
THREAD_PRIORITY_URGENT_AUDIO // -19
THREAD_PRIORITY_DISPLAY      // -4
THREAD_PRIORITY_FOREGROUND   // -2
THREAD_PRIORITY_DEFAULT      // 0
THREAD_PRIORITY_BACKGROUND   // 10
THREAD_PRIORITY_LOWEST       // 19
1
2
3
4
5
6
7
8
9
10
11
12

# 10.3 iOS QoS 全链路

   QoS Class(高到低):
   userInteractive → userInitiated → default → utility → background
      ↓
   GCD 内部映射到 nice 值 + cgroup
      ↓
   XNU 调度器按 QoS 分配 CPU
1
2
3
4
5
6

iOS QoS 传播:

  • 主线程默认 userInteractive
  • DispatchQueue 继承 QoS
  • 锁等待时支持优先级继承

# 10.4 优先级反转与继承

优先级反转:

   线程 H(高优先级)想取锁 L
      ↓
   线程 L(低优先级)持有锁 L
      ↓
   线程 M(中优先级)抢占了 L 的 CPU
      ↓
   H 等 L → L 等 M → H 间接等 M
   "高优先级被中优先级延误"
1
2
3
4
5
6
7
8

优先级继承:

   H 等 L 时:
      ↓ 临时把 L 的优先级提升到 H
      ↓ M 不能再抢 L 的 CPU
      ↓ L 快速完成 → 释放锁
      ↓ H 获得锁
      ↓ L 优先级恢复
1
2
3
4
5
6

收益(§17.4 实验):高负载时 P99 从 850ms → 35ms(24× 改善)。

# 10.5 关键路径的 QoS 策略

   关键路径(开播 / 支付 / 启动):
   ├─ 主线程:userInteractive
   ├─ 直接子线程:userInitiated
   └─ 持锁线程:启用优先级继承
   
   后台路径(统计 / 日志 / 预取):
   ├─ 全部 background QoS
   └─ 限流 + 超时
1
2
3
4
5
6
7
8

▶▶ 回扣 §02 案例:Day 4 推流相关线程升 QoS + 锁启用优先级继承,让推流锁等待 P99 从 850ms 降到 18ms。异构 SoC 时代优先级反转是隐形 P0。

探索性思考:为什么"优先级反转"在异构 SoC(大小核)时代被放大?因为大小核 = 不同 IPC = 同样 nice 值的"实际优先级"差异巨大。一个 background 任务在大核可能跑得比 userInitiated 在小核还快。异构架构破坏了"优先级"的单一序关系 —— 这是工程界还在适应的新现实。


# 11.协程全链路

# 11.1 Kotlin 协程全链路

   ① launch { ... } 创建 Coroutine
      ↓
   ② 编译期转换为状态机
      ↓
   ③ Continuation 表示"剩余计算"
      ↓
   ④ Dispatcher 决定在哪线程跑:
      - Dispatchers.Main(主线程)
      - Dispatchers.IO(默认 64 线程池)
      - Dispatchers.Default(CPU 核数池)
      ↓
   ⑤ suspend 函数挂起:
      - 不阻塞线程
      - 把线程还给池
      ↓
   ⑥ 恢复时:
      - Continuation.resume()
      - 重新调度到 Dispatcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

协程优势:

  • 一个 Dispatcher 可以跑成千上万协程
  • 挂起不阻塞线程(线程被复用)
  • 代码看似同步,实际异步

# 11.2 Swift async-await 全链路

   ① async func 返回 Task
      ↓
   ② await 处编译期生成 Continuation
      ↓
   ③ Task 在 GCD 池上跑
      ↓
   ④ await 时挂起
      - 不阻塞 GCD 线程
      ↓
   ⑤ 恢复时调度回原 Actor / Queue
1
2
3
4
5
6
7
8
9
10

Swift 特殊性:

  • @MainActor 标记主线程代码
  • Task.detached 跳出 Actor
  • async let 并行子任务

# 11.3 Go goroutine 全链路

   ① go func() 创建 goroutine
      ↓
   ② Go runtime 管理 M:N 调度
      - M 个 OS 线程
      - N 个 goroutine(N 远大于 M)
      ↓
   ③ goroutine 阻塞(如 channel)
      - 让出 OS 线程
      - OS 线程调度其他 goroutine
      ↓
   ④ 唤醒时重新调度
1
2
3
4
5
6
7
8
9
10
11

Go 特性:

  • goroutine 栈仅 2KB(按需增长)
  • channel 是 CSP 模型核心
  • runtime.GOMAXPROCS 控制 OS 线程数

# 11.4 协程 vs 线程对照

维度 线程 协程
创建开销 50μs+ 几 μs
栈大小 1MB 几 KB(按需)
切换开销 50μs(含缓存污染) < 1μs
调度方 OS 应用 runtime
适合场景 CPU bound IO bound + 高并发

# 11.5 协程的边界

协程不是银弹:

  • CPU 密集任务用协程没有优势(仍受 CPU 核数限制)
  • 协程依然需要线程池(CoroutineDispatcher)
  • 协程取消机制需要 lifecycle-aware

▶▶ 回扣 §02 案例:Day 3 IO 类任务全部改协程,配合 Dispatchers.IO 池(20 线程),让 IO 并发不阻塞 CPU。

探索性思考:为什么协程在 IO 场景胜过"每 IO 一线程"?因为协程是"用户态调度"——挂起不需要陷入内核。线程是 OS 的抽象,协程是 runtime 的抽象 —— 越靠近用户态,开销越小。但代价是 CPU 密集任务无法躲开 OS 调度(因为它必须真正占 CPU 核)。


# 12.跨端对照

# 12.1 五个全链路总览

链路 Android iOS Web Go
主线程 UI Thread + Looper Main Thread + RunLoop Main JS + 事件循环 (无强制)
线程池 ExecutorService GCD Worker pool M:N runtime
同步原语 synchronized / ReentrantLock os_unfair_lock Atomics sync.Mutex / channel
优先级调度 Process.setThreadPriority QoS Class (浏览器决定) (runtime 决定)
协程 Kotlin coroutines Swift async async/await goroutine

# 12.2 各平台优化优先级

Android:

  1. StrictMode 拦截主线程 IO
  2. 集中线程池服务
  3. 锁粒度拆分 + 锁内禁 IO
  4. 关键路径升 QoS + 优先级继承
  5. Kotlin 协程替代回调

iOS:

  1. 主线程禁同步等待
  2. GCD QoS 用对
  3. os_unfair_lock_with_options 优先级继承
  4. async-await 替代 callback

Web:

  1. 长任务切片(< 50ms / 任务)
  2. Web Worker 处理 CPU 密集
  3. requestIdleCallback 推迟非紧急
  4. async-await 简化代码

# 12.3 反直觉问题答疑

问题 答案
16 线程比 4 快 4 倍? 不一定,CPU bound 拐点在核数+1
Thread.sleep(1) 真睡 1ms? 不一定,最少 1ms 但常更长
CPU 100% 是好事? 看是哪个线程:前台是好,后台是坏
synchronized vs Lock 哪个快? JIT 优化后 synchronized 不一定慢
子线程 IO 没影响? 错。会消耗 IO 带宽 + 切换开销
GCD 是无限线程吗? 不是,有线程爆炸警告
Web Worker 越多越快? 错,创建开销 ~10ms
后台优先级低无影响? 错。仍占 CPU 时间片

▶▶ 回扣 §02 案例:经验派 100% 命中"反直觉问题"——把"线程多 = 快"当真理。真相是:线程是物理资源,不是免费午餐。


# 13.治理一层主线程

核心命题:主线程是用户感知的入口。任何阻塞主线程的任务都是 P0 问题。

# 13.1 StrictMode 拦截

机理:把"主线程做 IO/DB/网络"从约定变成强制约束。

代码:

if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
        .detectDiskReads().detectDiskWrites().detectNetwork()
        .penaltyDeath()  // 直接 crash 暴露
        .build());
}
1
2
3
4
5
6

收益:开发期 100% 拦截,杜绝线上事故。

边界:仅 Debug 启用 penaltyDeath;Release 用 penaltyLog 收集。

# 13.2 长任务切片 + IdleHandler

机理:单条主线程消息 > 16ms 必然掉帧;切成 < 8ms 段并推迟到空闲。

代码:

Looper.myQueue().addIdleHandler(() -> {
    if (pendingTasks.isEmpty()) return false;
    long deadline = SystemClock.uptimeMillis() + 4;
    while (!pendingTasks.isEmpty() && SystemClock.uptimeMillis() < deadline) {
        pendingTasks.poll().run();
    }
    return !pendingTasks.isEmpty();
});
1
2
3
4
5
6
7
8

收益:§02 案例开播状态机改 IdleHandler 后主线程 P99 从 200ms 降到 8ms。

边界:IdleHandler 在持续操作时不触发,需 postDelayed 兜底。

# 13.3 协程 / async-await 简化异步

机理:§17.5 实验证明协程在 IO 场景 5× 吞吐。

代码:

viewModelScope.launch {
    val data = withContext(Dispatchers.IO) { 
        db.userDao().getAll() 
    }
    adapter.submitList(data)  // 自动回主线程
}
1
2
3
4
5
6

收益:代码量减半,无回调地狱。

边界:CoroutineScope 必须 lifecycle-aware(viewModelScope/lifecycleScope)防内存泄漏。

# 13.4 Web 长任务切片

// 反例:长 for 循环阻塞主线程
for (let i = 0; i < 100000; i++) {
    process(items[i]);  // 全部同步
}

// 正例:用 scheduler.postTask 切片
async function processChunked(items) {
    for (let i = 0; i < items.length; i += 100) {
        await new Promise(resolve => 
            scheduler.postTask(() => {
                for (let j = i; j < Math.min(i + 100, items.length); j++) {
                    process(items[j]);
                }
                resolve();
            }, { priority: 'background' })
        );
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 13.5 主线程减负检查清单

  • [ ] StrictMode 已开启(Debug)
  • [ ] 启动期 IO 全部异步
  • [ ] JSON 解析不在主线程
  • [ ] 数据库查询不在主线程
  • [ ] 网络请求不在主线程
  • [ ] Bitmap 解码不在主线程
  • [ ] 长任务 > 8ms 切片
  • [ ] 非紧急用 IdleHandler

探索性思考:为什么"主线程减负"是所有 UI 优化的"大头"?因为主线程是"独木桥"——只有一个,所有 UI 工作必经。优化主线程 = 优化整个用户体验 —— 这就是为什么"启动期主线程 -300ms"能换"留存 +0.5%"。

▶▶ 回扣 §02 案例:Day 3 第 1 层"主线程减负"是关键起点——开播状态机切片到 IdleHandler 立刻让主线程 P99 -96%。


# 14.治理二层同步

核心命题:让锁短、让锁少、让锁不反转。

# 14.1 锁粒度拆分(按维度分锁)

机理:§02 案例推流准备锁被音频/视频/字幕/特效 4 路争用,拆成 4 把独立锁后 P99 850ms→18ms。

代码:

// 反例:1 把大锁
synchronized(streamLock) {
    audio.prepare(); 
    video.prepare(); 
    subtitle.prepare(); 
    effect.prepare();
}

// 正例:拆分锁
synchronized(audioLock) { audio.prepare(); }
synchronized(videoLock) { video.prepare(); }
synchronized(subtitleLock) { subtitle.prepare(); }
synchronized(effectLock) { effect.prepare(); }
1
2
3
4
5
6
7
8
9
10
11
12
13

收益:4 路完全并发,总耗时降到 max(各路) 而非 ∑。

边界:拆锁不能破坏原本需保护的状态一致性。

# 14.2 锁内严禁 IO/网络/Binder

机理:锁内 IO 等于把锁持有时长拉到秒级。

反例:

synchronized(this) {
    // ❌ 锁内做 IO
    val data = file.readBytes()  // 可能 100ms+
    cache.put(key, data)
}
1
2
3
4
5

正例:

val data = file.readBytes()  // 锁外读
synchronized(this) {
    cache.put(key, data)  // 锁内只做最小操作
}
1
2
3
4

收益:单次锁持有时长从 ms 级降到 μs 级。

# 14.3 ReadWriteLock / CopyOnWrite

机理:缓存类场景写很少、读极多。RWLock 让读完全并发。

代码:

private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Map<String, User> cache = new HashMap<>();

User get(String k) {
    rwLock.readLock().lock();
    try { return cache.get(k); }
    finally { rwLock.readLock().unlock(); }
}

void put(String k, User v) {
    rwLock.writeLock().lock();
    try { cache.put(k, v); }
    finally { rwLock.writeLock().unlock(); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

收益:读吞吐量提升 N 倍(N=并发读线程数)。

边界:写线程会阻塞所有读线程;写多场景反而比 mutex 慢。

# 14.4 CAS / 原子操作消灭锁

机理:计数器、状态机等场景用 AtomicInteger / LongAdder 比 synchronized 快 10-100×。

代码:

// 反例
synchronized(this) { counter++; }

// 正例(高并发优于 AtomicInteger)
LongAdder counter = new LongAdder();
counter.increment();
1
2
3
4
5
6

收益:高并发计数场景吞吐 +5-10×。

边界:仅适合单变量;复杂状态用 mutex。

# 14.5 全局锁顺序避免死锁

机理:§09.5 死锁四要素;破"循环等待"最实际。

// 全局规定:所有锁按 hash 升序加
void transfer(Account a, Account b, int amount) {
    Account first = a.id < b.id ? a : b;
    Account second = a.id < b.id ? b : a;
    synchronized(first) {
        synchronized(second) { /* ... */ }
    }
}
1
2
3
4
5
6
7
8

收益:完全杜绝特定模式的死锁。

边界:编译期无法强制;需 code review 把关。

探索性思考:为什么"锁治理"比"无锁编程"更现实?因为无锁编程对开发者要求极高(CAS、ABA、内存模型),出错代价是数据竞争。大多数业务场景下,正确使用锁 > 错误使用无锁。但锁的复杂度天花板低——超过 3 把锁的系统必须考虑无锁/消息传递重构。

▶▶ 回扣 §02 案例:Day 3 第 2 层"锁拆分 + 锁内禁 IO"两招组合,让推流锁等待 P99 从 850ms 直接降到 18ms。


# 15.治理三层线程池

核心命题:集中管理、分类隔离、显式回收。

# 15.1 集中线程池服务

机理:§02 案例与 §18.1 共同证明集中管理是治膨胀的根本手段。

代码:

object AppThreadService {
    val cpuPool = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors() + 1
    )
    val ioPool = Executors.newFixedThreadPool(20)
    val highPriorityPool = Executors.newFixedThreadPool(4).also {
        // 提升优先级(具体实现略)
    }
    
    fun submitCpu(r: Runnable) = cpuPool.submit(r)
    fun submitIo(r: Runnable) = ioPool.submit(r)
}
1
2
3
4
5
6
7
8
9
10
11
12

收益:§02 案例线程数 187→52。

边界:第三方 SDK 不可控;需 hook Thread.start 或在 review 中拦截。

# 15.2 CPU 池 = availableProcessors() + 1

机理:§17.2 拐点实验证明此值最优。

代码:

val cpuPool = ThreadPoolExecutor(
    Runtime.getRuntime().availableProcessors(),
    Runtime.getRuntime().availableProcessors() + 1,
    60L, TimeUnit.SECONDS,
    LinkedBlockingQueue()
)
1
2
3
4
5
6

收益:自适应设备核数,避免硬编码。

边界:异构 SoC 上"核数"包含小核,CPU bound 任务建议用大核数。

# 15.3 IO 池容量按峰值估算

机理:IO 任务大多时间在等,可以多于核数。典型 20-50。

收益:IO 并发不阻塞 CPU 计算。

边界:> 64 会引发资源耗尽。

# 15.4 HandlerThread / 线程显式回收

机理:HandlerThread 不 quit 会一直占栈和 TCB。

代码:

private HandlerThread handlerThread;

void start() {
    handlerThread = new HandlerThread("Worker");
    handlerThread.start();
}

void stop() {
    handlerThread.quitSafely();
    try { handlerThread.join(); }
    catch (InterruptedException ignored) {}
    handlerThread = null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

收益:线程数稳态收敛。

边界:quitSafely 后队列剩余任务会被丢弃,需保证幂等。

# 15.5 SDK 强制接入

机理:很多 SDK 默认自己创建线程,必须强制接入应用统一池。

手段:

  • 编译期 hook(ASM)替换 new Thread
  • 启动期反射替换 SDK 内部线程池
  • 与 SDK 厂商沟通改造

探索性思考:为什么"集中线程池"是治理线程膨胀的银弹?因为它解决了"组合爆炸"——10 个 SDK 各自 5 线程 = 50 线程;统一接入后所有 SDK 共享 20 线程 = 20。集中化是工程上最常见的"减法" —— 但每个个体(SDK)都觉得自己的需求合理,需要全局视角才能看到浪费。

▶▶ 回扣 §02 案例:Day 3 第 3 层"统一线程池 + SDK 接入"是案例中线程数从 187 降到 52 的核心动作——单步收益巨大。


# 16.治理四层调度

核心命题:异构 SoC + QoS 时代下优先级反转是隐形 P0。让关键路径不被压制。

# 16.1 优先级继承锁

机理:§17.4 实验长尾 -24×。

代码(iOS):

os_unfair_lock_lock_with_options(&lock,
    OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION);
1
2

代码(Android):

// Kotlin Mutex(带优先级感知)
suspend fun update() = mutex.withLock { /* ... */ }
1
2

收益:§02 案例推流锁等待 P99 850ms→18ms。

边界:仅对"主线程会等"的锁有意义;不要无脑全开。

# 16.2 关键路径线程升 QoS

机理:开播/支付/启动等关键路径的子线程也要升级,避免被压制。

代码(Android):

val thread = Thread(task).apply {
    priority = Thread.MAX_PRIORITY  // 10
}
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY)
1
2
3
4

代码(iOS):

let queue = DispatchQueue(label: "key.path", qos: .userInteractive)
1

收益:关键路径线程获得更多 CPU 时间片。

边界:不能滥用——所有线程都 high 等于都 default;只升关键 10-20%。

# 16.3 后台任务降 QoS + 限流

机理:后台任务(统计上报/日志/预取)应该让出 CPU 给前台。

代码:

val bgPool = Executors.newFixedThreadPool(2) { r ->
    Thread(r).apply {
        priority = Thread.MIN_PRIORITY  // 1
    }
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
1
2
3
4
5
6

收益:前台帧率更稳,发热下降。

边界:降级后任务可能饥饿;需配合超时机制。

# 16.4 大小核绑定(线程亲和性)

机理:CPU 密集任务绑大核;后台轻任务绑小核。

代码(Android NDK):

// 通过 sched_setaffinity 绑核
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(4, &cpuset);  // 绑大核 4
CPU_SET(5, &cpuset);  // 大核 5
sched_setaffinity(0, sizeof(cpuset), &cpuset);
1
2
3
4
5
6

收益:异构 SoC 上吞吐 +10-30%。

边界:手动绑核会与系统调度器冲突,需小心。多数业务无需细到这层。

# 16.5 ROI 排序

ROI 优化项 收益 成本 风险 对应章节
极高 StrictMode + 主线程拦截 杜绝线上事故 1 天 零 §13.1
极高 集中线程池服务 线程数 -50% 1-2 周 中 §15.1
极高 锁粒度拆分 关键锁 P99 -90% 1-2 周 中 §14.1
极高 协程 + 长任务切片 主线程 P99 -90% 1-2 周 中 §13.2-13.3
高 优先级继承锁 异构 SoC 长尾 -90% 几天 低 §16.1
高 CPU 池 = 核数+1 计算吞吐 +30% 几小时 低 §15.2
高 锁内禁 IO/网络 锁持有 -90% 1-2 周 中 §14.2
高 CAS / LongAdder 高并发计数 +5× 几天 低 §14.4
中 关键路径升 QoS 关键场景长尾改善 1 周 中 §16.2
中 ReadWriteLock 读多场景吞吐提升 1 周 中 §14.3
中 HandlerThread 显式 quit 防资源泄漏 几天 低 §15.4
低 大小核亲和性 异构吞吐 +10% 1-2 周 高(冲突) §16.4
极低 自己写调度器 几乎无收益 极高 极高 -

# 16.6 避免反向收益

  • 加更多线程治线程问题:§02 案例第 1 周翻车的根因。
  • spinlock 替代 mutex:CPU 烧到 100%,发热严重。
  • 过度异步:所有方法都 async,调用链长,调试困难。
  • 过度线程池分类:分成 10+ 个池反而资源浪费。
  • 所有线程升 QoS:等价于没升 QoS。

探索性思考:为什么"调度治理"是最被低估的层级?因为它的收益隐藏在长尾——平均看不出来,P99 才看得出来。调度问题是"少数派的问题" —— 90% 用户感受不到,10% 用户骂得很凶。但这 10% 的用户往往是关键场景(支付、推流),代价巨大。

▶▶ 回扣 §02 案例:Day 4 第 4 层"QoS + 优先级继承"是案例中"长尾杀手"——把推流锁等待 P99 从 850ms 直接降到 18ms(24× 改善)。


# 17.求证实验 ⭐

本节是"为什么这些优化生效"的实证基础。每个实验严格遵循"6 步求证法"。

# 17.1 实验一:线程切换代价

猜想:线程切换代价可忽略。

假设:单次切换的"裸"代价 1-10μs(调度本身),加上缓存污染后实际影响 10-100μs。

数学推导:

   调度路径   ~1μs(内核执行 schedule())
   TCB ops    ~0.5μs(保存 / 恢复寄存器)
   TLB 失效   ~2-10μs(地址空间变更)
   L1/L2 冷   ~10-100μs(实际内存访问变慢)
   ──────────────────
   合计 13–110μs,典型 ~50μs
1
2
3
4
5
6

执行(Pixel 6,两个线程交替 ping-pong):

场景 单次切换代价(μs)
同核绑定(缓存暖) 2.1
跨核(缓存污染) 18
跨集群(大小核切换) 55
主线程被打断(重 UI) 80

验证:

  • 同核(缓存暖)2-3μs 与公式一致。
  • 跨核切换主要被缓存污染主导。
  • 大小核切换最高(涉及不同 ISA 微架构)。

思考:

  • 线程切换代价从 2μs 到 80μs 不等,平均约 50μs。
  • 任务粒度 < 100μs 时,切换开销已经接近任务本身耗时,异步化无意义。
  • 小任务(< 100μs)不要异步;一系列小任务应合并再异步。

# 17.2 实验二:线程池拐点

猜想:线程数越多越快。

假设:CPU bound 最优 = CPU 大核数 + 1;超过后切换开销超过并发收益。

执行(Pixel 6,4 大核 + 4 小核,100 个 CPU 任务):

线程数 完成时间 (ms) 速度提升
1 10,200 1×
2 5,150 1.98×
4 2,650 3.85×
6 2,250 4.5×
8 2,100 4.86×
12 2,180 4.68×
16 2,280 4.47×
32 2,650 3.85×

验证:

  • 8 线程是峰值(4.86× 接近理论上限)。
  • 32 线程比 8 线程慢 26%。

思考:

  • CPU bound 任务的最优线程数 ≈ 大核数 + 1。
  • 超过这个数线性劣化。
  • 不要"为了快"开 32 / 64 线程,反向收益。

▶▶ 回扣 §02 案例:经验派第 1 周"把线程池从 8 调到 32"完全踩中本实验"32 比 8 慢 26%"的反例。

# 17.3 实验三:锁竞争代价

猜想:锁代价可忽略。

假设:高竞争下吞吐量随线程数下降;单线程延迟随 N 线性上升。

执行(临界区 10μs):

线程数 吞吐量 (op/s) 单线程平均延迟 (μs)
1 100,000 10
2 95,000 21
4 90,000 44
8 88,000 91
16 85,000 188

验证:

  • 吞吐量基本不变(被锁串行化)。
  • 单线程延迟随 N 线性上升。
  • 16 线程时单线程延迟达 188μs,相当于 18× 慢。

思考:

  • 高竞争锁会让"并行"退化为"串行 + 排队"。
  • 缩小锁粒度 比加更多线程有效。
  • 锁内严禁 IO、网络、GC 触发操作。

# 17.4 实验四:优先级反转

猜想:优先级继承收益不明显。

假设:优先级继承临时提升后台线程优先级,主线程等待降到 < 20ms。

执行:

实现 无干扰 P99 高负载干扰 P99 极端干扰 P99
A 普通 mutex 12 ms 180 ms 850 ms
B 优先级继承 12 ms 22 ms 35 ms

验证:

  • 无干扰时两者一致。
  • 高负载时 B 比 A 快 8 倍。
  • 极端干扰时 B 比 A 快 24 倍。

思考:

  • 优先级反转在异构 SoC 上非常常见。
  • 任何"主线程会等"的锁都必须启用优先级继承。
  • 继承代价 < 1%,但可避免 5-25 倍长尾。

# 17.5 实验五:协程 vs 线程

猜想:协程和线程性能差不多。

假设:协程在 IO 场景吞吐相当甚至更优(无切换开销),内存占用 1/100。

执行(1000 个 HTTP GET):

实现 总耗时 内存峰值 上下文切换/s 失败率
A 1000 线程 5.2 s 1.1 GB 32,000 8.2%(OOM)
B 协程(Dispatcher 20) 1.8 s 45 MB 2,800 0%
C 线程池 20 + 回调 1.9 s 48 MB 3,200 0%

验证:

  • A 直接 OOM 8.2%。
  • B 和 C 性能接近,但 B 代码简洁。

思考:

  • IO 场景必须用协程或固定线程池。
  • "每 IO 一线程"是反模式。
  • 协程的工程价值在于代码简洁,性能与线程池相当。
  • 协程不能替代线程池——底层仍是线程池在跑。

# 17.6 五大实验启示

   线程切换代价      → 50μs 量级,小任务异步不划算       ─┐
   线程池拐点        → CPU bound 最优 = 核数             │
   锁竞争代价        → 高竞争锁让并行退化为串行          ├─▶ 线程优化 = 找拐点 + 防反转 + 选模型
   优先级反转 / 继承 → 异构 SoC 必启用,省 5-25× 长尾   │
   协程 vs 线程      → IO 场景协程 5×+ 吞吐、1/24 内存    ─┘
1
2
3
4
5

统一启示:

  • 并发不是越多越好:所有线程问题都在找"收益 vs 成本"的拐点。
  • 数据驱动配置:线程池大小不是拍脑袋,要根据设备和场景算。
  • 少而精 > 多而乱:少量但精心管理的线程,胜过大量随意创建的线程。
  • 优先级反转是隐形杀手:异构 SoC + QoS 普及,必须主动防御。
  • 选对模型胜过堆资源:IO 用协程、CPU 用线程池、UI 用单线程事件循环。

▶▶ 回扣 §02 案例:方法派 7 天闭环每一步都对应本节实验——Day 1 三方案组合 + Day 3 拐点 + 锁拆分 + Day 4 优先级继承。实验是优化前的"必经之路"。


# 18.实战案例

# 18.1 跨端同构案例:SDK 线程膨胀

背景:某社交应用启动期开了 100+ 线程,启动慢 + 内存高。Android / iOS / Web 都有类似情况。

现象:

  • Android:线程清单显示 120 个线程,启动期 CPU 切换 8000+/s
  • iOS:GCD 队列数达 80+,启动期切换密集
  • Web:50+ Worker(其中很多是空闲)

根因:每个 SDK 启动期都自己创建线程,没人统一管理。

治理:

  • Android:建立 AppThreadService,所有 SDK 强制接入
  • iOS:统一 GCD QoS 池,禁止 SDK 自建队列
  • Web:Worker pool 复用,禁止每次新建

效果:

平台 线程数(before) 线程数(after) 启动期切换/s
Android 120 45 8000 → 2000
iOS 80(队列) 30 6000 → 1500
Web 50(Worker) 8 N/A

核心洞察:集中线程池是治理的根本手段——三端思路统一。

# 18.2 平台特异案例:iOS GCD 主队列死锁

背景:某 App 在主队列上调用 dispatch_sync 导致死锁,触发 watchdog kill。

根因:

// ❌ 死锁:在主队列上 sync 调用主队列
DispatchQueue.main.async {
    DispatchQueue.main.sync {  // 死锁!
        // ...
    }
}
1
2
3
4
5
6

治理:

  • 严禁 DispatchQueue.main.sync 任何调用
  • 用 lint 规则强制拦截
  • 已有 if Thread.isMainThread { ... } else { DispatchQueue.main.sync { ... } } 判断

效果:iOS watchdog kill 率 -95%。

洞察:特定平台的"特殊死锁模式"需要专门防御。Android 上没有这种死锁(Looper 是异步的)。

# 18.3 反例案例:CachedThreadPool 引发线程爆炸

背景:某 App 网络层用 Executors.newCachedThreadPool,认为"弹性最好"。

结果:

  • 高并发请求时创建上千线程
  • 内存爆涨 + OOM
  • 每 10 个用户就有 1 个崩溃

修复:

// ❌ 原始
val pool = Executors.newCachedThreadPool()

// ✅ 修复
val pool = ThreadPoolExecutor(
    20, 30, 60L, TimeUnit.SECONDS,
    LinkedBlockingQueue(100),  // 有界队列
    ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略:调用者执行
)
1
2
3
4
5
6
7
8
9

洞察:默认线程池永远不要直接用——newCachedThreadPool / newFixedThreadPool 都需要 review 配置。


# 19.防劣化体系

# 19.1 三道防线总览

   ┌────────────┐     ┌────────────┐     ┌────────────┐
   │ 编码期 Lint │  →  │ CI 卡口     │  →  │ 线上 SLO    │
   │ IDE 即时提示│     │ Macrobench  │     │ 监控告警    │
   └────────────┘     └────────────┘     └────────────┘
1
2
3
4

# 19.2 编码期 Lint

自定义规则:

  • 主线程 IO/网络(StrictMode + Lint 双重)
  • new Thread / new HandlerThread 直接创建(建议用 AppThreadService)
  • ExecutorService 未 shutdown
  • 锁内 IO 调用
  • DispatchQueue.main.sync 调用
  • Executors.newCachedThreadPool 使用(建议直接构造 ThreadPoolExecutor)

# 19.3 CI 卡口

性能基线测试:

@Test
fun threadCountBenchmark() = benchmarkRule.measureRepeated(
    metrics = listOf(TraceSectionMetric("thread_count")),
    iterations = 5
) {
    startActivityAndWait()
    repeat(20) { testKeyAction() }
}
1
2
3
4
5
6
7
8

卡口规则:

  • 启动期线程数退化 ≥ 10% → 阻断 PR
  • 上下文切换/s 退化 ≥ 20% → 警告
  • 主线程 P99 退化 ≥ 10% → 阻断 PR

# 19.4 线上 SLO

指标 目标 告警阈值
进程线程数稳态 < 60 > 100
上下文切换/s < 5000 > 10000
主线程消息 P99 < 16ms > 50ms
锁等待 P99 < 5ms > 50ms
ANR 率 < 0.05% > 0.1%

# 19.5 文化建设

  • 线程预算:新模块必须申报"线程数预算"
  • 线程 Code Review:线程相关 PR 必有专人 review
  • 线程 OKR:线程数稳态进 OKR

探索性思考:为什么"线程防劣化"特别难?因为线程问题是"组合性的"——单个模块加 5 线程没问题,10 个模块加 5 线程就爆炸。线程问题需要全局视角才能发现——这就是为什么必须有"线程委员会"或集中服务。


# 20.跨平台速查

# 20.1 工具速查

平台 静态采集 系统追踪 锁竞争 协程
Android /proc/pid/task Perfetto Perfetto Lock Kotlin Coroutines
iOS sysctlbyname Instruments System Trace Instruments Threads Swift async-await
Web navigator.hardwareConcurrency Performance Panel (无原生) async/await
Linux ps -L perf perf lock (无)

# 20.2 关键 API 速查

目的 Android iOS Web
线程池 ExecutorService DispatchQueue.global Worker pool
CPU 池大小 availableProcessors() + 1 system navigator.hardwareConcurrency
互斥锁 synchronized / ReentrantLock os_unfair_lock Atomics.wait
优先级继承 Kotlin Mutex os_unfair_lock_with_options (无)
提升 QoS THREAD_PRIORITY_DISPLAY DispatchQoS.userInteractive (浏览器决定)
协程 Kotlin coroutines Swift async async/await
长任务切片 IdleHandler DispatchSourceTimer scheduler.postTask
主线程检查 Looper.getMainLooper() Thread.isMainThread window === self

# 20.3 各平台优化清单

Android:

  • [ ] StrictMode 开启
  • [ ] AppThreadService 集中管理
  • [ ] CPU 池 = 核数+1,IO 池 = 20
  • [ ] 锁粒度按维度拆分
  • [ ] 锁内禁 IO/网络
  • [ ] 关键路径升 QoS
  • [ ] HandlerThread 显式 quit
  • [ ] 用 Kotlin 协程替代回调
  • [ ] Lint 自定义规则

iOS:

  • [ ] 主队列禁 sync 调用
  • [ ] GCD QoS 用对(不滥用 userInteractive)
  • [ ] os_unfair_lock_with_options 优先级继承
  • [ ] async-await 替代 callback
  • [ ] Instruments 定期 review

Web:

  • [ ] 长任务 < 50ms / 任务
  • [ ] Web Worker 处理 CPU 密集
  • [ ] requestIdleCallback 推迟非紧急
  • [ ] Worker pool 复用
  • [ ] async-await 简化代码

# 21.总结与延伸

# 21.1 五条核心原则

  1. 线程不是免费的:每个线程消耗内存 / TCB / 切换 / 缓存。
  2. 找拐点而非堆资源:CPU 池最优 = 核数 + 1,超过即劣化。
  3. 锁短 / 锁少 / 锁不反转:锁粒度拆分 + 锁内禁 IO + 优先级继承。
  4. 集中管理胜过分散自治:统一线程池服务是治膨胀的银弹。
  5. 选对模型胜过堆资源:IO 用协程、CPU 用线程池、UI 用单线程事件循环。

# 21.2 五个常见误区

误区 真相
"线程越多越快" 错。CPU bound 拐点在核数+1,超过线性劣化
"spinlock 总比 mutex 快" 错。临界区 > 1μs 时 CPU 烧 100%
"synchronized 比 Lock 慢" 错。JIT 优化后差距很小
"GCD 是无限线程" 错。系统决定,可能爆炸
"后台优先级低无影响" 错。仍占 CPU 时间片,且可能优先级反转

# 21.3 一句话总结

线程是"用 CPU 兑换延迟"的工具,但线程不是免费的。线程优化的本质是找拐点(不是堆资源)、防反转(异构 SoC 的隐形 P0)、选对模型(IO 用协程 / CPU 用线程池 / UI 用单线程)。所有线程问题都映射到"调度不当 / 同步开销 / 资源浪费"三类——分类施治才能精准。集中管理(AppThreadService)+ 锁治理(拆分+禁 IO+优先级继承)+ 关键路径 QoS 是工程上的标配组合。

# 21.4 延伸阅读

  • 卷二·01 CPU 监控与分析:线程是 CPU 的使用者
  • 卷二·02 内存监控与治理:每个线程占栈 1MB
  • 卷二·03 OOM 与低内存治理:线程数耗尽是 D 类 OOM
  • 卷三·03 卡顿捕获与归因:线程问题导致卡顿
  • 卷三·04 ANR 监控与治理:死锁 → ANR
  • 卷四·05 功耗与电量优化:线程数 → 切换 → 功耗

下一篇预告:卷二·05 进程与多进程优化 —— 比线程更重的"隔离"工具。

上次更新: 2026/06/07, 10:26:12
OOM与低内存治理
进程与多进程优化

← OOM与低内存治理 进程与多进程优化→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式