阅读视图

发现新文章,点击刷新页面。

主线程Runloop

Runloop循环的代码,不是在主线程上运行的吗 Runloop循环的代码,不是在主线程上运行的吗 Runloop循环的代码,不是在主线程上运行的吗

聊聊swift闭包

Swift闭包语法详解 代码示例 详细解释 这是一个Swift中的闭包(Closure)语法,具体解释如下: 代码分解 语法分析 变量声明: var b:声明一个变量 b :() -> Void:指定

1-24.【GCD】QoS 和线程优先级是 1:1 映射的吗?

QoS 和线程优先级并不是 1:1 映射,它们只是相关,但机制上存在差别。下面详细解释:


1️⃣ 核心结论

QoS 是 GCD 的任务级别优先策略,用于指导系统调度任务和线程资源;线程优先级(pthread / sched)是操作系统层面线程调度权重。二者关联,但并非严格一一对应。

换句话说:

  • QoS → 告诉系统“任务重要性”,GCD 根据这个在全局线程池中选择或创建线程
  • 线程优先级 → 控制 CPU 时间分配
  • 高 QoS 通常对应较高线程优先级,但不是绝对,也会受到系统调度和能耗优化影响

2️⃣ 关联机制

2.1 任务到线程的映射

当你提交一个任务到队列:

  1. GCD 查看任务 QoS

  2. 选择或创建合适线程

    • 高 QoS → 尝试在高优先级线程或更多线程上执行
    • 低 QoS → 使用低优先级线程池或延迟执行
  3. 线程可能共享

    • 同一线程可以执行不同 QoS 任务
    • 系统会动态调整线程优先级以匹配任务 QoS

⚠️ 所以一个线程上可能交替执行不同 QoS 的任务,线程优先级会动态调整,并非固定 1:1


2.2 系统动态调节

  • macOS/iOS 内核会综合考虑:

    • 当前 CPU 利用率
    • 能耗和电量策略
    • 多任务调度公平性
  • 因此:

    • 高 QoS 任务不一定始终在最高优先级线程上执行
    • 低 QoS 任务可能在空闲线程上抢占 CPU

3️⃣ 与 pthread 优先级的区别

特性 QoS (GCD) 线程优先级 (pthread / sched)
粒度 任务级别 线程级别
作用 指导系统调度任务和线程分配 决定线程在 CPU 上调度权重
线程绑定 任务可能在任何线程上执行 固定线程调度权重
动态性 GCD 可动态提升/继承 QoS 通常固定,除非手动修改
对能耗影响 高 QoS → 可能更激进分配 CPU 无直接能耗优化机制

4️⃣ QoS 优先级与线程优先级关系

GCD 文档说明:

  • .userInteractive / .userInitiated → 高 QoS → 系统会尽量在高优先级线程或更多线程执行
  • .utility / .background → 低 QoS → 系统可能延迟执行或降低线程 CPU 优先级
  • 线程优先级只是 一个调度参考,实际调度会动态调整

✅ 核心:高 QoS 更可能获得高线程优先级,但不是一条任务对应一条线程的固定关系


5️⃣ 总结一句话

QoS 影响 GCD 如何选择线程和调度任务,但不是 1:1 映射到线程优先级;线程优先级只是系统层面的调度权重,GCD 会动态调整线程以匹配任务 QoS。


1-22.【GCD】使用 DispatchSource 时最容易踩的坑有哪些?

1️⃣ 忘记 resume()

let source = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
source.schedule(deadline: .now(), repeating: 1.0)
source.setEventHandler { print("Timer fired") }
// source.resume() // ⚠️ 忘记 resume
  • :创建 DispatchSource 后必须调用 resume() 才会启动事件源
  • 原因:DispatchSource 默认是暂停状态
  • 后果:事件永远不会触发
  • 解决:调用 resume();如果需要暂停/恢复,结合 suspend() 使用

2️⃣ DispatchSource 被释放

DispatchSource.makeTimerSource(queue: .global()).schedule(deadline: .now(), repeating: 1.0).setEventHandler {
    print("Fired")
}
  • :DispatchSource 没有被强引用 → 立即释放
  • 原因:DispatchSource 是对象,如果没有外部引用,系统会释放它
  • 后果:事件永远不会触发
  • 解决:保留一个强引用,例如类属性
class MyClass {
    var timer: DispatchSourceTimer?
    
    func start() {
        timer = DispatchSource.makeTimerSource(queue: .global())
        timer?.schedule(deadline: .now(), repeating: 1.0)
        timer?.setEventHandler { print("Fired") }
        timer?.resume()
    }
}

3️⃣ 线程安全误区

  • :认为事件处理 block 内访问的资源线程安全

  • 原因:DispatchSource 的回调在指定队列上执行,并发队列上可能同时执行多个事件 block

  • 后果:共享资源竞争、数据不一致

  • 解决

    • 串行队列保证顺序和互斥
    • 并发队列访问共享资源时加锁或 barrier

4️⃣ 重复 resume 导致 crash

source.resume()
source.resume() // ⚠️ 再次 resume 会 crash
  • :DispatchSource 只能 resume 一次

  • 原因:resume 用来启动事件源,重复调用会触发异常

  • 解决

    • 使用标志位判断是否已 resume
    • 或通过类属性封装管理状态

5️⃣ 使用 sync 触发死锁

let queue = DispatchQueue(label: "serial")
let source = DispatchSource.makeTimerSource(queue: queue)
source.setEventHandler {
    queue.sync { print("Deadlock") } // ⚠️ 死锁
}
source.resume()
  • :事件 block 内调用 sync 队列同队列 → 死锁
  • 原因:串行队列同步调用自己会等待 → 永远无法完成
  • 解决:使用 async 调度,或者将事件 block 放到不同队列

6️⃣ 忘记 cancel

  • :DispatchSource 用完不取消
  • 原因:DispatchSource 会保持队列引用,未 cancel 可能导致内存泄漏
  • 解决
source.cancel()
source.setEventHandler(nil) // 释放闭包引用

7️⃣ 对定时器误用 resume/suspend

  • :误认为 suspend/resume 可以无限暂停定时器

  • 注意

    • DispatchSourceTimer 初始为暂停状态 → 必须 resume 启动
    • suspend/resume 必须成对使用,否则下一次 resume 会 crash

8️⃣ 队列选择不当

  • :将 DispatchSource 绑定到主队列或串行队列,但事件量大

  • 后果

    • 阻塞主线程 → UI 卡顿
    • 串行队列上高频事件 → 队列积压
  • 解决

    • 高频事件使用后台并发队列
    • 根据场景合理选择队列类型

✅ 总结常见坑及解决策略

解决方案
忘记 resume 必须调用 resume() 启动事件源
DispatchSource 被释放 保持强引用(类属性)
线程安全误区 并发队列访问共享资源加锁,或使用串行队列
重复 resume 导致 crash 只 resume 一次,使用标志位
sync 导致死锁 避免在事件 block 内对同队列 sync,使用 async
忘记 cancel 使用完毕后 cancel,并释放事件处理器闭包
suspend/resume 不成对 严格成对调用,确保队列状态正确
队列选择不当 高频事件用后台并发队列,UI 事件用主队列

💡 核心经验

  1. DispatchSource 本质是 事件源 + GCD block → 避免直接对线程或队列做 unsafe 操作
  2. 所有事件处理 block 都遵循绑定队列调度 → 理解串行/并发对共享资源的影响
  3. 强引用 + resume + cancel 是生命周期管理三部曲

❌