不是说runloop是无限循环吗,为啥主线程还会休眠?
Runloop 的 “无限循环” 和 “休眠” 并不矛盾,反而这两个特性是 Runloop 设计的核心精髓 ——Runloop 的无限循环是 “带休眠的循环”,不是空转的死循环,休眠正是为了让无限循环不消耗多余资源。
简单说结论:主线程 Runloop 的 while (1) 是 “逻辑上的无限循环”,但循环内部会主动触发线程休眠,直到有事件唤醒才继续执行循环体,而非一直空转执行代码。
一、先破误区:“无限循环”≠“一直执行代码”
普通的空转死循环是这样的(会把 CPU 占满):
// 纯空转死循环:CPU 100%占用,线程一直跑
while (1) {
// 什么都不做,持续循环
}
而Runloop 的无限循环是 “智能循环” ,伪代码更接近这样:
// Runloop的无限循环:有任务就执行,无任务就休眠
while (1) {
// 1. 检查是否有待处理的事件/任务
if (有任务) {
处理任务(事件、渲染、回调等);
} else {
// 2. 无任务时,让线程进入「休眠状态」,释放CPU资源
内核层面让线程休眠,等待唤醒信号;
}
}
Runloop 的 “无限” 体现在这个 while (1) 的逻辑永远不退出(主线程 Runloop 默认永不停止),但 ** 循环体的执行是 “按需触发”** 的 —— 无任务时根本不执行循环体的核心逻辑,而是让线程休眠。
二、关键原理:Runloop 如何实现 “休眠 - 唤醒”(内核层面)
Runloop 的休眠不是自己实现的,而是基于操作系统内核的 IO 多路复用机制(iOS/macOS 下是mach_msg,底层封装了 select/poll/kqueue),这是跨平台事件驱动的通用方案,核心流程分 3 步:
- 无任务时,Runloop 调用
mach_msg向内核发送 “休眠请求” :主线程会从运行态切换为休眠态(也叫等待态),此时线程不会占用 CPU 的任何时间片,CPU 可以去处理其他线程 / 进程的任务,资源消耗几乎为 0; - 有事件时,内核主动唤醒线程:当有能唤醒 Runloop 的事件出现时(如 CADisplayLink 刷新、触摸事件、网络回调、定时器),内核会向该线程发送唤醒信号,触发
mach_msg返回,线程从休眠态切回运行态; - 唤醒后继续执行循环体:Runloop 接收到唤醒信号后,继续执行 while (1) 的循环体,处理对应的任务,完成后再次检查是否有新任务,无任务则再次休眠。
对主线程来说:CADisplayLink(屏幕刷新定时器)是最常见的唤醒源(60Hz 下 16.67ms 唤醒一次),其次是用户的触摸、滑动等 UI 事件。
三、结合帧渲染理解:休眠 - 唤醒 - 渲染的完整循环(60Hz)
把 Runloop 的休眠、唤醒和帧渲染结合,主线程的完整工作流程就非常清晰了(无其他 UI 事件时):
-
休眠阶段:主线程 Runloop 无任务,调用
mach_msg进入休眠,CPU 释放; -
定时唤醒:16.67ms 后,CADisplayLink 触发,内核唤醒主线程,Runloop 的 while (1) 继续执行;
-
任务检查:Runloop 检查是否有 UI 更新标记(CATransaction);
- ✅有更新:执行布局→绘制→合成→显示的一帧渲染流程,完成后再次检查无新任务,进入休眠;
- ❌无更新:直接跳过渲染阶段,再次调用
mach_msg进入休眠;
-
重复循环:每 16.67ms 被 CADisplayLink 唤醒一次,重复上述步骤。
四、额外补充:为什么 Runloop 要设计 “休眠” 机制?
如果主线程 Runloop 的无限循环没有休眠,而是一直空转检查任务,会导致两个严重问题:
- CPU 占用率飙升:一个空转的 while (1) 会把单个 CPU 核心占满(100%),主线程是核心线程,会直接导致设备发热、耗电、卡顿;
- 系统资源浪费:CPU 一直被 Runloop 的空转占用,无法分配给其他线程 / 进程(如后台网络、音频播放),整个 APP 的运行效率会大幅降低。
而休眠机制完美解决了这两个问题:无任务时线程不占 CPU,有任务时及时唤醒,实现了 “按需执行,资源最优”,这也是 Runloop 能成为 iOS 事件驱动核心的关键原因。
最终一句话总结
主线程 Runloop 是逻辑上的无限循环(while (1) 永不退出),但物理上的执行是按需的—— 无任务时通过内核让线程休眠,有事件(如屏幕刷新、UI 触摸)时被唤醒继续执行循环体,休眠和无限循环是 Runloop 的两个核心特性,而非矛盾点。