Runloop的无限循环

21 阅读4分钟

不是说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 步:

  1. 无任务时,Runloop 调用mach_msg向内核发送 “休眠请求” :主线程会从运行态切换为休眠态(也叫等待态),此时线程不会占用 CPU 的任何时间片,CPU 可以去处理其他线程 / 进程的任务,资源消耗几乎为 0;
  2. 有事件时,内核主动唤醒线程:当有能唤醒 Runloop 的事件出现时(如 CADisplayLink 刷新、触摸事件、网络回调、定时器),内核会向该线程发送唤醒信号,触发mach_msg返回,线程从休眠态切回运行态;
  3. 唤醒后继续执行循环体:Runloop 接收到唤醒信号后,继续执行 while (1) 的循环体,处理对应的任务,完成后再次检查是否有新任务,无任务则再次休眠。

对主线程来说:CADisplayLink(屏幕刷新定时器)是最常见的唤醒源(60Hz 下 16.67ms 唤醒一次),其次是用户的触摸、滑动等 UI 事件。

三、结合帧渲染理解:休眠 - 唤醒 - 渲染的完整循环(60Hz)

把 Runloop 的休眠、唤醒和帧渲染结合,主线程的完整工作流程就非常清晰了(无其他 UI 事件时):

  1. 休眠阶段:主线程 Runloop 无任务,调用mach_msg进入休眠,CPU 释放;

  2. 定时唤醒:16.67ms 后,CADisplayLink 触发,内核唤醒主线程,Runloop 的 while (1) 继续执行;

  3. 任务检查:Runloop 检查是否有 UI 更新标记(CATransaction);

    • 有更新:执行布局→绘制→合成→显示的一帧渲染流程,完成后再次检查无新任务,进入休眠;
    • 无更新:直接跳过渲染阶段,再次调用mach_msg进入休眠;
  4. 重复循环:每 16.67ms 被 CADisplayLink 唤醒一次,重复上述步骤。

四、额外补充:为什么 Runloop 要设计 “休眠” 机制?

如果主线程 Runloop 的无限循环没有休眠,而是一直空转检查任务,会导致两个严重问题:

  1. CPU 占用率飙升:一个空转的 while (1) 会把单个 CPU 核心占满(100%),主线程是核心线程,会直接导致设备发热、耗电、卡顿;
  2. 系统资源浪费:CPU 一直被 Runloop 的空转占用,无法分配给其他线程 / 进程(如后台网络、音频播放),整个 APP 的运行效率会大幅降低。

休眠机制完美解决了这两个问题:无任务时线程不占 CPU,有任务时及时唤醒,实现了 “按需执行,资源最优”,这也是 Runloop 能成为 iOS 事件驱动核心的关键原因。

最终一句话总结

主线程 Runloop 是逻辑上的无限循环(while (1) 永不退出),但物理上的执行是按需的—— 无任务时通过内核让线程休眠,有事件(如屏幕刷新、UI 触摸)时被唤醒继续执行循环体,休眠和无限循环是 Runloop 的两个核心特性,而非矛盾点。