Swift Concurrency:彻底告别“线程思维”,拥抱 Task 的世界

原文:Threads vs. Tasks in Swift Concurrency 链接:www.avanderlee.com/concurrency...

前言:别再问"它跑在哪个线程?"

在 GCD 时代,我们习惯用 DispatchQueue.global(qos: .background).async { ... }DispatchQueue.main.async { ... } 来显式地把任务丢到指定线程。久而久之,形成了一种"线程思维":

"这段代码很重,我要放到子线程。"

"这行 UI 代码必须回到主线程。"

Swift Concurrency(async/await + Task)出现以后,这套思维需要升级------系统帮你决定"跑在哪个线程"。我们只需关心"任务(Task)"本身。

线程(Thread)到底是什么?

  • 系统级资源:由操作系统调度,创建、销毁、切换开销大。
  • 并发手段:多线程可以让多条指令流同时跑。
  • 痛点:数量一多,内存占用高、上下文切换频繁、优先级反转。

Swift Concurrency 的目标就是让我们 不再直接面对线程。

Task:比线程更高级的抽象

  • 一个 Task = 一段异步工作单元。
  • 不绑定线程:Task 被放进 合作线程池(cooperative thread pool),由运行时动态分配到"刚好够用"的线程上。
  • 运行机制:
    1. 线程数量 ≈ CPU 核心数。
    2. 遇到 await(挂起点)时,当前线程被释放,可立即执行其他 Task。
    3. 挂起的 Task 稍后可能在另一条线程恢复。

代码示范:Task 与线程的"若即若离"

swift 复制代码
struct ThreadingDemonstrator {
    private func firstTask() async throws {
        print("Task 1 started on thread: \(Thread.current)")
        try await Task.sleep(for: .seconds(2))   // 挂起点
        print("Task 1 resumed on thread: \(Thread.current)")
    }

    private func secondTask() async {
        print("Task 2 started on thread: \(Thread.current)")
    }

    func demonstrate() {
        Task {
            try await firstTask()
        }
        Task {
            await secondTask()
        }
    }
}

典型输出(每次都可能不同):

arduino 复制代码
Task 1 started on thread: <NSThread: 0x600001752200>{number = 3, name = (null)}
Task 2 started on thread: <NSThread: 0x6000017b03c0>{number = 8, name = (null)}
Task 1 resumed on thread: <NSThread: 0x60000176ecc0>{number = 7, name = (null)}

解读:

  • Task 1 在 await 时释放了线程 3;
  • Task 2 趁机用到了线程 8;
  • Task 1 恢复时,被安排到线程 7------前后线程可以不同。

线程爆炸(Thread Explosion)还会发生吗?

场景 GCD Swift Concurrency
同时发起 1000 个网络请求 可能创建 1000 条线程 → 内存暴涨、调度爆炸 最多 CPU 核心数条线程,其余任务挂起 → 无爆炸
阻塞线程 线程真被 block,CPU 空转 用 continuation 挂起,线程立刻服务别的任务

因此,线程爆炸在 Swift Concurrency 中几乎不存在。

线程更少,性能反而更好?

  • GCD 误区:线程越多,并发越高。
  • 真相:线程 > CPU 核心时,上下文切换成本激增。
  • Swift Concurrency 做法
    • 线程数 = 核心数;
    • 用挂起/恢复代替阻塞;
    • CPU 始终在跑有效指令,切换开销极低。

实测常见场景(CPU-bound & I/O-bound)下,Swift Concurrency 往往优于 GCD。

三个常见误区

误区 正解
每个 Task 会新开一条线程 Task 与线程是多对一,由调度器动态复用
await会阻塞当前线程 await会挂起任务并释放线程
Task 一定按创建顺序执行 执行顺序不保证,取决于挂起点与调度策略

思维升级:从"线程思维"到"任务思维"

线程思维 任务思维
"这段代码要在子线程跑" "这段代码是异步任务,系统会调度"
"回到主线程刷新 UI" "用 @MainActorMainActor.run标记主界面任务"
"我怕线程太多" "线程数系统自动管理,我专注业务逻辑"

小结

  1. 线程是低层、昂贵的系统资源。
  2. Task 是高层、轻量的异步工作单元。
  3. Swift Concurrency 通过合作线程池 + 挂起/恢复机制,让线程数始终保持在"刚好够用",既避免线程爆炸,又提升性能。
  4. 开发者应把注意力从"线程"转向"任务"与"挂起点"。

当你下次再想问"这段代码跑在哪个线程?"时,提醒自己:

"别管线程,写正确的 Task 就行。"

相关推荐
HarderCoder2 小时前
深入理解 Swift 中的 async/await:告别回调地狱,拥抱结构化并发
swift
HarderCoder3 小时前
深入理解 SwiftUI 的 ViewBuilder:从隐式语法到自定义容器
swiftui·swift
HarderCoder3 小时前
在 async/throwing 场景下优雅地使用 Swift 的 defer 关键字
swift
东坡肘子4 小时前
我差点失去了巴顿(我的狗狗) | 肘子的 Swift 周报 #098
swiftui·swift·apple
Swift社区15 小时前
Swift 实战:实现一个简化版的 Twitter(LeetCode 355)
leetcode·swift·twitter
HarderCoder15 小时前
当Swift Codable遇到缺失字段:优雅解决数据解码难题
swift
YungFan2 天前
iOS26适配指南之UIButton
ios·swift
麦兜*3 天前
【swift】SwiftUI动画卡顿全解:GeometryReader滥用检测与Canvas绘制替代方案
服务器·ios·swiftui·android studio·objective-c·ai编程·swift
Swift社区4 天前
Swift 实战:从数据流到不重叠区间的高效转换
开发语言·ios·swift