当前位置: 首页 > news >正文

RN线程模型

RN 用线程隔离解决了"JS 单线程 + UI主线程限制 + Native 耗时操作"三者之间的冲突,代价是所有跨线程操作都必须异步。理解这个,就理解了 RN 大多数性能问题的根源。


一、线程概览

在RN旧架构中,一般有3个线程:

  • UI / Main Thread
  • JS Thread
  • Shadow Thread

在RN新架构里,很多文章或分享会把运行时相关工作拆得更细,具体如下:

线程源码名称职责
JS 线程"js"/"JavaScript"执行所有 JavaScript 代码、处理 JS 回调
Native Module 线程"native_modules"执行 Native 模块方法调用
UI 线程(主线程)"main_ui"/dispatch_get_main_queue()所有 View 更新、UI 操作
其他系统线程Fabric / RuntimeScheduler新架构引入,辅助渲染调度
源码依据:
  • JS 线程:ReactCommon/react/runtime/platform/ios/ReactCommon/RCTJSThreadManager.mm:13
  • Native Module 线程:ReactAndroid/.../bridge/queue/ReactQueueConfigurationSpec.kt:49
  • UI 线程:ReactAndroid/.../bridge/queue/MessageQueueThreadSpec.kt:23

二、为什么需要多线程

三件事性质完全不同,放在一起会互相阻塞甚至死锁。

JS 线程为什么要独立

JavaScript 引擎是单线程的,必须独占一个线程。如果和 UI 共用主线程,一段复杂 JS 逻辑跑起来,用户触摸屏幕没反应,动画会卡帧。

源码中 JS 线程被设置为最高优先级,还分配了 2 倍栈空间:

// RCTCxxBridge.mm:434_jsThread.qualityOfService=NSOperationQualityOfServiceUserInteractive;

Native Module 线程为什么要独立

Native 方法经常是耗时操作(文件 I/O、网络、加密),如果在 JS 线程同步调用,JS 线程会被整个卡住。源码里有明确警告:

// RCTBridgeModule.h:220// WARNING: calling methods synchronously can have strong performance penalties// and introduce threading-related bugs to your native modules.

所以绝大多数 Native 调用都是异步派发:

// RCTNativeModule.mm:90dispatch_async(queue,block);// 派遣到 Native Module 的队列,不阻塞 JS

UI 线程为什么必须独立

iOS UIKit 强制要求所有 UI 操作只能在主线程执行,这是系统层面的约束。但 JS 不能同步调主线程,否则会死锁:

JS 线程等主线程完成 UI 操作 ↕ 同时 主线程等 JS 线程返回数据(用户交互触发) → 死锁

源码中有专门的断言宏强制检查线程归属:

// RCTAssert.h:106#defineRCTAssertMainQueue()// UI 代码必须在主线程#defineRCTAssertNotMainQueue()// JS 代码不能在主线程

不隔离的后果

线程不隔离会怎样
JS 线程JS 逻辑跑起来 → UI 冻结,触摸无响应
Native Module 线程耗时 Native 操作 → JS 卡死
UI 线程JS 同步调用主线程 → 双方互等 → 死锁

三、各线程工作特点

JS 线程:CFRunLoop 驱动 + 批处理

驱动方式:由 CFRunLoop 驱动,任务通过CFRunLoopPerformBlock入队,执行完唤醒 RunLoop 继续处理:

// RCTMessageThread.mmCFRunLoopPerformBlock(m_cfRunLoop,kCFRunLoopCommonModes,^{func();});CFRunLoopWakeUp(m_cfRunLoop);

批处理机制:JS 调用 Native 不是逐条发送,而是批量收集后一次性通过callFunctionReturnFlushedQueue发出,由isEndOfBatch标记触发onBatchComplete。目的是减少跨线程通信次数,类似浏览器的微任务批处理。

Native Module 线程:串行独立队列 + 异步回调

每个模块有独立的串行 GCD 队列

  • GCD队列:指的是 iOS/macOS 里的Grand Central Dispatch任务调度队列。
// RCTModuleData.mm_methodQueue=dispatch_queue_create("com.facebook.react.XxxQueue",DISPATCH_QUEUE_SERIAL);
  • 串行:保证单个模块的调用顺序
  • 独立:不同模块之间可以并行执行
  • 模块可重写methodQueue自定义队列

调用默认异步,结果通过RCTResponseSenderBlock回调传回 JS。只有极少数特殊模块(queue == RCTJSThread)才同步执行。

UI 线程:Shadow Tree 布局 + 批量 flush

两阶段设计

  1. 布局计算(非主线程):JS 描述的 UI 转成 ShadowView,由 Yoga 计算布局,生成 UIBlock 放入_pendingUIBlocks
  2. UI 更新(主线程):CADisplayLink 每帧触发,批量 flush UIBlocks 到主线程更新真实 UIView
// RCTUIManager.mm[_pendingUIBlocks addObject:block];// 非主线程:收集变更for(blockinpreviousPendingUIBlocks){// 主线程:批量执行block(self.viewRegistry);}

CADisplayLink 是整个系统的节拍器,每帧(~16.7ms)同时触发 JS 批处理和 UI flush,两者解耦但同频。


四、线程协作关系

用户触摸 → 主线程 → 转发事件给 JS Thread JS Thread → 批量调用 → Native Module Queues → callback 回 JS JS Thread → UI 变更描述 → ShadowView 计算 → UIBlocks → 主线程更新 View CADisplayLink 每帧驱动上述全流程

三个线程不直接通信,全部通过队列异步传递,这是线程安全的根本保障。

UI Thread / Main Thread

Native Module Queues(各模块独立串行队列)

JS Thread(CFRunLoop)

触发源

每帧触发 JS flush

每帧触发 flushUIBlocks

touch 事件

事件回调转发

dispatch_async

dispatch_async

callback / Promise

callback / Promise

UI 变更描述

addUIBlock

dispatch_async main

CADisplayLink
每帧 ~16.7ms

用户交互
touch / gesture

执行 JS 逻辑
setState / 事件处理

批处理队列
flushedQueue

模块A
dispatch_queue

模块B
dispatch_queue

ShadowView
Yoga 布局计算(非主线程)

_pendingUIBlocks
待提交队列

真实 UIView
更新渲染


五、新旧架构差异

旧架构(Bridge)新架构(JSI/Fabric)
JS ↔ Native 通信异步,必须跨线程序列化JSI 直接调用,减少线程跳跃
Native Module 线程iOS 每个模块可自定义methodQueue统一收敛
渲染线程UIManager 在主线程Fabric 独立调度

六、对实际开发的意义

1. 卡顿排查有方向

  • JS 线程繁忙 → 动画掉帧、交互延迟
  • 主线程繁忙 → 触摸无响应

2. 动画为什么要用 Reanimated

  • Animated跑在 JS 线程,JS 一忙就掉帧
  • Reanimated把动画逻辑移到 UI 线程,彻底绕开 JS,这是线程模型决定的根本差异

3. Native 回调为什么必须异步

  • 方法跑在模块自己的队列里,不在 JS 线程,结果只能通过 callback / Promise 回传

4. setState 之后为什么不立即生效

  • UI 更新是批量 flush 的,不是同步生效,依赖 UI 结果要放在useEffectonLayout

5. 线程和实际开发的关系

  • 老架构:
    • 点击响应慢、页面初始化慢、列表计算重,优先怀疑 JS 线程;
    • 原生转场卡顿、滚动掉帧、图片和复杂视图导致的卡顿,优先怀疑主线程/UI 线程;
      • 原生转场卡顿:通常指页面切换过程中由原生侧负责的过渡效果出现掉帧、顿挫或不跟手。
    • Native Module/TurboModule 的具体执行线程取决于模块实现,不应先假定它固定跑在某一条线程。
  • 新架构下:
    • 多数普通业务更新仍主要受 JS 线程影响,但高优先级交互在某些场景下可由 UI 线程同步推进,因此比旧架构更利于即时交互响应。
http://www.jsqmd.com/news/595046/

相关文章:

  • mbed OS USB串口缓冲库:线程安全环形缓冲设计
  • SEO_掌握核心SEO技巧,让你的流量翻倍
  • AI开发-python-langchain框架(--word文档加载 )
  • 基于Kintex UltraScale+ XCKU5P的Cameralink图像采集与HDMI实时显示系统设计
  • 2026年质量好的亚克力摇摇乐/亚克力销售厂家推荐 - 品牌宣传支持者
  • 如何用VLLM和GPT-OSS-20B搭建一个天气查询工具?完整代码分享
  • 精准控制:gemma-3-12b-it在OpenClaw复杂指令下的执行边界测试
  • OpenClaw+千问3.5-35B-A3B-FP8:智能邮件分类与回复系统
  • 2026年04月05日最热门的开源项目(Github)
  • 基于大数据与深度学习的二手房价格预测系统设计与实现-完整源码论文毕设项目
  • HarmonyOS ArkTS开发实战:用Axios封装一个带拦截器的网络请求工具类
  • Windows下OpenClaw安装指南:对接Qwen3.5-9B-AWQ-4bit镜像
  • windows安装 Claude Code CLI 工具
  • 8舵机蜘蛛机器人嵌入式运动控制库设计
  • OpenClaw任务编排:百川2-13B-4bits模型处理依赖型复杂工作流
  • 靠专业建议收咨询费!传统旅游顾问转型AI行程规划师,如何在高定市场赚大钱
  • 论文精讲:谷歌Deepmind发表的ICLR 2025-测试时计算
  • SEO_网站SEO优化完整教程:从入门到精通
  • 3.30~4.5补题
  • STM32和ESP32摄像头接口深度对比:DCMI vs DVP在图像采集中的性能实测
  • 两台电脑如何通过局域网共享移动硬盘
  • 千问3.5-9B缓存策略:减少OpenClaw重复任务Token消耗
  • 模糊控制在运动控制中的实践指南——从算法原理到参数优化
  • Python 日志神器 Loguru 超详细使用教程
  • 避坑指南:用Pixhawk 4飞控连接Nooploop TOFSense激光雷达,这些线序错误千万别犯
  • OpenCLI vs agent-browser :小白也能懂的浏览器自动化指南
  • Anthropic 曝光 Claude“绝望代码“:2026 年,这 5 个 AI 创业机会正在闷声发大财
  • 职业院校智慧校园系统采购,为什么要把校企合作项目放在前面?
  • OpenClaw高Token消耗优化:Qwen3-32B私有镜像成本对比
  • 论文阅读:ICLR 2026 Towards Safe Reasoning in Large Reasoning Models via Corrective Intervention