Swift 基础语法
Multi Thread

多线程

一、什么是多线程?为什么要使用多线程?

多线程是指在同一个进程中同时执行多个线程,每个线程可并行处理不同任务。

使用多线程的目的:

  • 提升应用响应速度,防止主线程卡顿(如网络请求、图片处理)。
  • 充分利用多核 CPU,提高性能。
  • 分离耗时任务与 UI 操作,提升用户体验。

注意:

线程开销较大,过度创建可能导致资源竞争、切换开销、死锁等问题。

二、Swift 中常用的多线程方案

1. GCD(Grand Central Dispatch)

系统级的并发框架,轻量高效,自动管理线程。

2. Operation / OperationQueue

GCD 的面向对象封装,支持依赖、取消、优先级等功能。

3. Thread

底层方式,手动管理线程生命周期,不推荐日常使用。

4. Swift Concurrency(async/await)

Swift 5.5 新引入语法,支持结构化并发、Task、Actor 模型,更安全直观。

三、GCD 的核心概念

1. 队列(DispatchQueue)

任务的排队执行容器:

  • 串行队列(Serial):一次只执行一个任务。
  • 并行队列(Concurrent):可同时执行多个任务。

2. 同步与异步

  • sync:等待任务完成后继续执行。
  • async:立即返回,不阻塞当前线程。
let queue = DispatchQueue(label: "com.example.test")
queue.async {
    print("异步任务1")
}
queue.async {
    print("异步任务2")
}

四、主线程与主队列的区别

  • 主线程(Main Thread):负责 UI 更新与事件响应。
  • 主队列(DispatchQueue.main):运行在主线程上的串行队列。

示例:

DispatchQueue.main.async {
    // 更新 UI
}

五、GCD 的常见用法

1. 延迟执行

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    print("2秒后执行")
}

2. 栅栏(barrier)实现线程安全写操作

let queue = DispatchQueue(label: "com.example.queue", attributes: .concurrent)
queue.async(flags: .barrier) {
    // 写操作,互斥执行
}

3. 一次性执行(once)

static let shared: MyManager = {
    let instance = MyManager()
    return instance
}()

4. 任务组(DispatchGroup)

let group = DispatchGroup()
let queue = DispatchQueue.global()
 
group.enter()
queue.async {
    print("任务1")
    group.leave()
}
 
group.enter()
queue.async {
    print("任务2")
    group.leave()
}
 
group.notify(queue: .main) {
    print("所有任务完成")
}

六、Operation 与 OperationQueue

Operation 表示单个任务;OperationQueue 负责调度与执行。

优点:

  • 支持任务依赖(addDependency)
  • 可取消任务(cancel())
  • 可监听状态(isExecuting, isFinished)
  • 可设置优先级与并发数
let queue = OperationQueue()
let op1 = BlockOperation { print("任务1") }
let op2 = BlockOperation { print("任务2") }
 
op2.addDependency(op1)
queue.addOperations([op1, op2], waitUntilFinished: false)

七、GCD 与 OperationQueue 区别

对比项GCDOperationQueue
编程风格C API面向对象
依赖关系不支持支持 addDependency
取消任务不支持支持 cancel
状态监听支持 isFinished 等
可扩展性高,可继承自定义

八、Swift Concurrency(async/await)

Swift 5.5 引入的结构化并发机制,使异步代码更易读。

优势:

  • 写法接近同步逻辑,消除回调地狱。
  • 自动线程调度与生命周期管理。
  • 支持 Task、TaskGroup、Actor。
func fetchData() async throws -> String {
    try await Task.sleep(nanoseconds: 1_000_000_000)
    return "数据返回"
}
 
Task {
    let result = try await fetchData()
    print(result)
}

九、死锁及避免方式

1. 死锁定义

多个线程互相等待对方释放资源,导致程序永久阻塞。

2. 常见死锁场景

let queue = DispatchQueue(label: "com.test")
queue.sync {
    queue.sync { print("死锁") }
}

原因: 同一串行队列内,外层任务未完成又同步等待自身。

3. 解决思路

  • 避免在同一串行队列中使用 sync 嵌套。
  • 合理使用异步调用。
  • 谨慎使用锁,防止双重竞争。

十、线程安全的常见手段

1. 串行队列

一次只执行一个任务。

2. DispatchSemaphore(信号量)

控制同时访问资源的线程数。

let semaphore = DispatchSemaphore(value: 1)
DispatchQueue.global().async {
    semaphore.wait()
    print("安全访问资源")
    semaphore.signal()
}

3. 锁机制

如 NSLock、RecursiveLock、os_unfair_lock 等。

4. DispatchBarrier

在并行队列中强制任务互斥执行。

十一、线程间通信方式

  • 主线程回调:
DispatchQueue.global().async {
    let data = fetchData()
    DispatchQueue.main.async {
        updateUI(data)
    }
}
  • NotificationCenter:广播式通信。
  • Delegate / Closure:一对一回调。
  • Combine / AsyncStream:响应式数据流。

十二、常见扩展问法

Q1:GCD 的 global() 队列是什么?

系统提供的全局并行队列,适合执行通用异步任务。

DispatchQueue.global(qos: .userInitiated).async { ... }

Q2:QoS(Quality of Service) 是什么?

任务优先级等级:

  • .userInteractive:最高优先级,UI 相关。
  • .userInitiated:用户期望立即得到结果。
  • .utility:耗时任务,如下载。
  • .background:最低优先级,后台同步。

十三、Swift Concurrency 深入:Task、Actor、AsyncStream

一、Task 的概念与用法

Task 是 Swift 并发体系的基本执行单元,用于启动一个异步任务。

1. 创建 Task

Task {
    print("这是一个异步任务")
}

等价于在 GCD 中:

DispatchQueue.global().async { ... }

2. 带返回值的 Task

let task = Task {
    return try await fetchData()
}
 
let result = try await task.value

3. Task 的生命周期与取消

每个 Task 都可以被取消:

let task = Task {
    for i in 1...5 {
        try Task.checkCancellation()
        print("执行中:\(i)")
    }
}
 
task.cancel()

注意:

Task.checkCancellation() 不会自动抛错,而是主动检测是否被取消。

二、TaskGroup(任务组)

任务组允许并发执行多个子任务,并在所有任务完成后统一返回。

1. 使用示例

func fetchAll() async throws -> [String] {
    try await withThrowingTaskGroup(of: String.self) { group in
        let urls = ["a.com", "b.com", "c.com"]
 
        for url in urls {
            group.addTask {
                return try await fetchData(from: url)
            }
        }
 
        var results = [String]()
        for try await result in group {
            results.append(result)
        }
        return results
    }
}

优势:

  • 自动管理并发子任务。
  • 子任务抛出错误时可统一捕获。
  • 所有任务结束后自动清理资源。

三、Actor —— 线程安全的并发模型

Actor 是 Swift 的一种并发安全类型,类似于一个带锁的类,用于防止数据竞争。

1. 定义与使用

actor Counter {
    var value = 0
 
    func increment() {
        value += 1
    }
}
 
let counter = Counter()
Task {
    await counter.increment()
}

2. 为什么要用 Actor?

在传统多线程环境下,多个线程同时读写同一变量会造成数据不一致。

而 Actor 保证同一时间只能由一个任务访问其可变状态。

3. MainActor

@MainActor 表示该任务必须在主线程上执行,常用于 UI 更新。

@MainActor
func updateUI() {
    // 一定在主线程执行
}

四、AsyncStream —— 异步数据流

AsyncStream 用于在异步环境中逐步产生数据(类似 Combine 的 Publisher)。

1. 基本用法

func makeCounterStream() -> AsyncStream<Int> {
    AsyncStream { continuation in
        for i in 1...5 {
            continuation.yield(i)
        }
        continuation.finish()
    }
}

2. 读取数据

Task {
    for await value in makeCounterStream() {
        print("收到数据:\(value)")
    }
}

3. 实际应用场景

  • 网络请求分块下载
  • 实时事件监听(如 Socket、传感器数据)
  • UI 层监听异步回调流

五、AsyncLet 并发绑定

async let 可同时启动多个异步任务并在后续等待结果。

async let image = loadImage()
async let user = fetchUser()
async let posts = fetchPosts()
 
let (img, usr, pst) = await (image, user, posts)

相比 TaskGroup,async let 更轻量,适合数量固定的异步任务。

六、Task 优先级(Priority)

Swift 并发系统中可以指定任务的优先级,类似 GCD 的 QoS。

优先级描述
.high用户交互级别任务
.medium普通异步任务
.low后台任务或非关键任务
Task(priority: .high) {
    await doImportantWork()
}

七、传统 GCD 与 Swift Concurrency 对比

特性GCDSwift Concurrency
语法风格回调闭包async/await
错误处理手动回调try/throw 自动传播
依赖管理TaskGroup 管理
生命周期程序员管理系统自动管理
可读性回调地狱顺序逻辑,易读易维护
内存安全需手动同步Actor 自动保证

八、在 iOS 实际项目中的应用场景

  1. 网络请求并发
async let user = fetchUser()
async let profile = fetchProfile()
async let feed = fetchFeed()
let (u, p, f) = await (user, profile, feed)
  1. 可同时请求多接口,加速加载速度。

  2. 图片下载与缓存

    使用 AsyncStream 或 TaskGroup 管理多个下载任务,避免重复下载。

  3. UI 更新

    配合 @MainActor,确保界面修改在主线程安全执行。

  4. 后台同步任务

    使用低优先级 Task 或后台 actor 管理,节省资源。

九、面试延伸问题与思路

Q1:Swift Concurrency 与传统锁相比,性能如何?

A:Actor 内部自动调度访问,不使用传统锁,性能更高且避免死锁风险。

Q2:Task 与 GCD 的 async 区别?

A:Task 属于结构化并发,有生命周期和取消机制;GCD 是全局调度,缺少任务关系。

Q3:在 Actor 内调用非隔离函数会怎样?

A:需要 await 关键字,否则编译错误。Actor 强制同步点,确保数据一致性。

Q4:能否在 UIKit 中直接使用 async/await?

A:可以,从 iOS 15 开始原生支持。例如:

@MainActor
func updateUI() async {
    label.text = "加载完成"
}

十四、多线程面试常见陷阱与实战题(含代码分析)

一、经典陷阱题:GCD 死锁

题目:

print("1")
DispatchQueue.main.sync {
    print("2")
}
print("3")

结果与原因:

程序卡死(死锁)。

原因分析:

main.sync 会等待主队列中的任务执行完,但主队列本身当前就在执行这段代码,因此相互等待 → 死锁。

正确写法:

DispatchQueue.main.async {
    print("2")
}

输出顺序:1 → 3 → 2

二、异步与同步混用陷阱

题目:

let queue = DispatchQueue(label: "com.test")
print("A")
queue.async {
    print("B")
    queue.sync {
        print("C")
    }
    print("D")
}
print("E")

输出:

A
E
B
(死锁)

原因:

queue.async 异步执行 B,接着内部 queue.sync 又在同一串行队列中同步等待自己执行 → 死锁。

三、DispatchGroup 实战:并行任务同步

场景:

有三个网络请求,需要并发执行,全部完成后刷新 UI。

示例:

let group = DispatchGroup()
let queue = DispatchQueue.global()
 
group.enter()
queue.async {
    print("任务1完成")
    group.leave()
}
 
group.enter()
queue.async {
    print("任务2完成")
    group.leave()
}
 
group.enter()
queue.async {
    print("任务3完成")
    group.leave()
}
 
group.notify(queue: .main) {
    print("全部完成,更新UI")
}

输出:

任务1完成
任务3完成
任务2完成
全部完成,更新UI

说明任务是并发执行的。

四、信号量(Semaphore)实现顺序执行

场景:

多个异步任务需要按顺序执行

示例:

let semaphore = DispatchSemaphore(value: 0)
let queue = DispatchQueue.global()
 
queue.async {
    print("任务1开始")
    sleep(1)
    print("任务1结束")
    semaphore.signal()
}
 
semaphore.wait()
 
queue.async {
    print("任务2开始")
    sleep(1)
    print("任务2结束")
    semaphore.signal()
}
 
semaphore.wait()
 
print("全部结束")

输出:

任务1开始
任务1结束
任务2开始
任务2结束
全部结束

信号量通过“等待-释放”机制,实现顺序执行。

五、OperationQueue 实战题:依赖管理

题目:

三个任务:A、B、C。要求 A → B → C 顺序执行。

解法:

let queue = OperationQueue()
 
let opA = BlockOperation { print("A") }
let opB = BlockOperation { print("B") }
let opC = BlockOperation { print("C") }
 
opB.addDependency(opA)
opC.addDependency(opB)
 
queue.addOperations([opA, opB, opC], waitUntilFinished: false)

输出:

A
B
C

即使在并发队列中,也能通过依赖保证顺序。

六、常见死锁模式总结

场景示例原因
主队列中使用 syncDispatchQueue.main.sync {}等待自身执行
串行队列中嵌套 syncqueue.sync 内再 queue.sync自我等待
锁嵌套两线程互相等待互相持锁
信号量错误使用wait() 未配对 signal()永久阻塞

七、线程安全题目分析

题目:

var count = 0
let queue = DispatchQueue(label: "com.test", attributes: .concurrent)
 
for _ in 0..<10_000 {
    queue.async {
        count += 1
    }
}

问:结果可能是多少?

答:不确定。

多线程并发修改 count 会发生数据竞争(race condition),导致最终结果小于 10000。

解决方案:

方式一:使用串行队列

let queue = DispatchQueue(label: "com.test.serial")

方式二:使用信号量

let semaphore = DispatchSemaphore(value: 1)
queue.async {
    semaphore.wait()
    count += 1
    semaphore.signal()
}

方式三:使用 Actor(Swift 并发方式)

actor Counter {
    var count = 0
    func increment() { count += 1 }
}
 
let counter = Counter()
Task {
    await counter.increment()
}

八、Async/Await 面试实战题

题目:

如何让多个异步请求并发执行后汇总结果?

示例:

func loadUser() async -> String { "User" }
func loadPosts() async -> String { "Posts" }
 
func loadData() async {
    async let user = loadUser()
    async let posts = loadPosts()
 
    let (u, p) = await (user, posts)
    print("结果:\(u) + \(p)")
}

输出:

结果:User + Posts

面试思路:

  • async let 会立即启动异步任务。
  • await 等待所有任务完成。
  • 不同于串行执行,能充分利用并发。

九、经典考点:主线程异步与同步区别

题目:

DispatchQueue.main.async {
    print("A")
}
print("B")

输出顺序:B → A(异步,不阻塞主线程)

DispatchQueue.main.sync {
    print("A")
}
print("B")

会造成死锁(主线程等待自身执行)。

十、QoS(Quality of Service)优化实战

QoS 决定任务优先级与调度策略。

示例:

DispatchQueue.global(qos: .userInteractive).async {
    print("用户交互任务")
}
 
DispatchQueue.global(qos: .background).async {
    print("后台任务")
}

系统优先执行 .userInteractive 队列任务。

QoS 常见级别:

级别用途示例
.userInteractive即时响应用户操作UI 动画、滑动
.userInitiated用户主动触发操作加载数据
.utility耗时操作下载文件
.background后台维护任务日志上传

十一、面试常见开放题思路

Q1:如何保证线程安全?

  • 使用串行队列、锁、信号量、Actor。
  • 保证同一资源在同一时间只被一个线程修改。

Q2:多线程一定更快吗?

不一定。

线程切换开销高,轻量任务反而在单线程中更高效。

Q3:GCD 的内存管理策略?

任务在提交到队列时自动捕获上下文,系统管理线程池,无需手动释放。

Q4:如何避免竞争条件?

通过同步机制(串行化、锁、信号量)确保访问顺序。

Q5:async/await 与 Combine 的区别?

async/await 是语言层的语法糖,适合点对点异步逻辑。

Combine 是响应式框架,适合连续流式数据。

十二、总结

Swift 多线程体系经历了三个阶段演进:

  1. GCD → 轻量、底层、灵活。
  2. OperationQueue → 面向对象、支持依赖。
  3. Swift Concurrency → 安全、直观、结构化并发。

理解这三层的异同,是 iOS 面试中必考的核心知识。

十五、iOS 多线程优化与性能调度策略

一、为什么多线程反而会“变慢”?

多线程不是“越多越好”。

线程的创建、销毁和切换都需要系统资源。

当线程数过多时,会产生:

  • 上下文切换开销:CPU 不断保存、恢复寄存器状态。
  • 锁竞争:多个线程等待资源释放。
  • 缓存失效:不同核心间数据同步开销大。

优化原则:

多线程的目标是“最大化 CPU 有效工作时间”,而不是“最大化线程数量”。

二、主线程优化策略

主线程必须保持轻量,专注于 UI 和交互。

在 iOS 中,主线程阻塞超过 100ms 就可能导致明显卡顿。

常见优化点:

  1. 避免在主线程执行耗时操作
    • 网络请求、JSON 解析、图片压缩、数据库操作必须放在后台线程。
  2. UI 更新集中处理
    • 合并多次 UI 修改成一次统一刷新。
  3. 使用 Instruments 检测主线程卡顿
    • Time Profiler、Main Thread Checker。

三、GCD 性能优化技巧

1. 使用合适的队列类型

// 并行队列适合CPU密集任务
let concurrentQueue = DispatchQueue(label: "com.app.concurrent", attributes: .concurrent)
 
// 串行队列用于数据安全访问
let serialQueue = DispatchQueue(label: "com.app.serial")

2. 减少线程创建

重复使用系统提供的全局队列:

DispatchQueue.global(qos: .userInitiated).async { ... }

3. 使用

DispatchWorkItem

控制任务

let workItem = DispatchWorkItem {
    print("执行任务")
}
 
DispatchQueue.global().async(execute: workItem)
workItem.cancel() // 可取消任务

4. 批处理任务(DispatchGroup)

在多个任务之间实现并行 + 汇总处理,避免串行阻塞。

四、OperationQueue 调优策略

1. 限制并发数

queue.maxConcurrentOperationCount = 3

防止过多任务同时启动占满 CPU。

2. 利用依赖关系

通过 addDependency 建立清晰的执行链,减少同步等待。

3. 自定义 Operation

重写 main() 或使用 isCancelled 检查取消状态,增强控制力。

五、Swift Concurrency 调度优化

Swift 并发(async/await、Task、Actor)已经对线程调度进行了优化,但仍需遵守性能原则。

1. 避免过度嵌套 Task

// ❌ 不推荐
Task {
    Task {
        Task { ... }
    }
}

每个 Task 都会产生调度开销,应尽量使用结构化任务。

2. 使用

Task.detached

谨慎

Task.detached {
    await doSomething()
}

Detached Task 不继承父 Task 的上下文,适合真正独立的后台任务,但不宜滥用。

3. 善用

@MainActor

确保 UI 更新在主线程执行,减少锁竞争。

@MainActor func refreshUI() { ... }

六、线程安全与数据共享优化

1. 使用串行队列替代锁

let safeQueue = DispatchQueue(label: "com.app.safe")
 
safeQueue.async {
    // 安全访问共享资源
}

相比传统锁(NSLock),串行队列开销更小、可读性更高。

2. 使用读写锁(Dispatch Barrier)

适用于“多读少写”场景:

let queue = DispatchQueue(label: "rw.queue", attributes: .concurrent)
 
func read() {
    queue.async { print("读操作") }
}
 
func write() {
    queue.async(flags: .barrier) { print("写操作") }
}

3. 使用 Actor 替代锁(Swift Concurrency)

actor DataManager {
    private var data = [String]()
    func add(_ value: String) { data.append(value) }
}

Actor 自动隔离状态,线程安全且高效。

七、常见高性能场景优化案例

1. 图片加载与解码

  • 使用异步队列在后台解码图片,避免主线程阻塞。
  • 使用缓存(如 NSCache)减少重复解码。

2. 大量 JSON 解析

  • 将解析任务放入后台队列。
  • 使用 JSONDecoder 的流式解析避免内存峰值。

3. 滑动列表卡顿

  • 在后台计算布局(如高度、图片尺寸)。
  • 在主线程仅进行 UI 渲染。

4. 网络任务批量执行

利用 DispatchGroup 或 TaskGroup 并行请求,减少总耗时。

八、QoS(服务质量等级)调度策略

GCD 和 Swift Concurrency 都支持 QoS,用于动态调度优先级。

QoS 等级优先级场景
.userInteractive最高UI 动画、交互操作
.userInitiated用户主动请求
.utility下载、后台计算
.background后台同步、日志上传

示例:

DispatchQueue.global(qos: .background).async {
    saveLogs()
}

系统会根据设备负载自动调整调度频率和线程优先级。

九、异步性能调试工具

1.

Instruments – Time Profiler

分析 CPU 占用与线程调用栈,定位性能瓶颈。

2.

Thread Sanitizer

在 Xcode Scheme 设置中开启,检测数据竞争与死锁风险。

3.

Main Thread Checker

自动检测 UI 操作是否在主线程中执行。

4.

os_signpost / Instruments

自定义打点,分析任务执行时间:

import os.signpost
 
let log = OSLog(subsystem: "com.app", category: "performance")
let signpostID = OSSignpostID(log: log)
 
os_signpost(.begin, log: log, name: "HeavyTask", signpostID: signpostID)
// 执行任务
os_signpost(.end, log: log, name: "HeavyTask", signpostID: signpostID)

十、最佳实践总结

  1. 主线程只负责 UI 与事件

    任何耗时逻辑都必须异步执行。

  2. 控制并发量

    并发 ≠ 快速。避免线程暴增。

  3. 数据访问串行化

    保证一致性优先于速度。

  4. 任务解耦与依赖管理

    使用 OperationQueue 或 TaskGroup 管理复杂任务关系。

  5. 合理分配 QoS

    让系统理解任务的重要程度,调度更智能。

  6. 主动检测卡顿与竞争

    使用 Instruments 工具监控性能。

十一、面试开放题延伸方向

Q1:如何在 Swift Concurrency 中实现任务取消?

使用 Task.isCancelled 或 Task.checkCancellation() 手动检测。

for i in 1...10 {
    if Task.isCancelled { break }
    print(i)
}

Q2:如何避免“主线程阻塞”?

  • 后台线程执行耗时任务。
  • UI 操作封装为异步更新。
  • 分块加载大数据。

Q3:在多核设备中如何最大化性能?

  • 利用并行队列让任务在多个核心同时执行。
  • 避免频繁切换上下文。
  • 通过 QoS 平衡任务优先级。

十二、总结

多线程的真正考验不在于语法,而在于设计哲学

在保证线程安全的前提下,让 CPU 处于“最合理的忙碌状态”。

Swift 从 GCD 到 OperationQueue,再到 async/await,是从控制线程控制逻辑流的进化。

理解这一脉络,能让你在面试和实际项目中更从容地应对各种并发问题。