普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月29日iOS

4-1.【协议导向编程】什么是协议导向编程(POP),它解决了传统面向对象设计中的哪些问题?

作者 项阿丑
2026年1月29日 11:05

1️⃣ 什么是协议导向编程(POP)

协议导向编程 是 Swift 提出的编程范式,它的核心思想是:

  • 通过协议(protocol)定义接口和行为,而不是依赖类继承。
  • 通过协议扩展(protocol extensions)提供默认实现,减少重复代码。
  • 关注行为组合而非继承层级,强调“能力”的组合而不是类的层级关系。

简单理解:

在 POP 中,我们关注对象 能做什么(行为),而不是它 是什么(类型/类继承)。


Swift 中的协议示例

protocol Flyable {
    func fly()
}

protocol Swimmable {
    func swim()
}

// 给 Flyable 提供默认实现
extension Flyable {
    func fly() {
        print("I can fly!")
    }
}

// 使用协议组合
struct Bird: Flyable, Swimmable {
    func swim() {
        print("I can swim too!")
    }
}

let duck = Bird()
duck.fly()   // 输出: I can fly!
duck.swim()  // 输出: I can swim too!

✅ 这里你可以看到:

  • 没有继承自基类。
  • 通过协议组合实现多种能力。
  • 默认实现避免重复代码。

2️⃣ POP 解决了传统 OOP 的哪些问题

在传统面向对象编程(OOP)中,我们通常使用 类继承 来复用代码:

class Animal { ... }
class Bird: Animal { ... }
class Duck: Bird { ... }

OOP 的局限

  1. 继承层级僵化

    • 类只能单继承(Swift 中类也只支持单继承)。
    • 行为复用受限。
    • 如果想让一个类同时拥有多种能力(fly、swim、run),继承树会变得复杂。
  2. 代码复用困难

    • 父类提供通用方法,但子类可能需要修改。
    • 多个不相关类要重复实现相同功能。
  3. 类型耦合强

    • 子类依赖父类,导致灵活性下降。
    • 改变父类可能影响整个继承链。

POP 的优势

问题 POP 的解决方案
单继承限制 协议可以任意组合,轻松实现多能力组合(多重“继承”效果)
代码重复 协议扩展提供默认实现,避免重复实现
耦合性强 类型依赖协议接口而非具体类,实现低耦合
灵活性差 POP 强调行为组合,可在不改变继承关系的前提下扩展功能
测试难度大 使用协议作为依赖(依赖注入),单元测试更容易模拟/mock

Swift POP 的典型实践

  1. 使用协议定义能力(Abilities)

    • Flyable, Swimmable, Runnable 等。
  2. 协议扩展提供默认实现

    • 避免每个类型都重复实现。
  3. 通过协议组合创建对象

    • 组合多种能力而不依赖继承层级。
  4. 依赖抽象而非具体类型

    • 代码更灵活,可测试性高。

总结

协议导向编程(POP) 的核心思想是:

“面向协议编程,而不是面向类编程”
通过协议组合和扩展,实现行为复用和灵活的类型组合,从而解决了传统 OOP 的继承僵化、代码重复、耦合度高等问题。

Swift 官方甚至强调:

Swift 是协议导向语言,而不是类继承语言

Neo-Cupertino 档案:撕开 Actor 的伪装,回归 Non-Sendable 的暴力美学

2026年1月29日 10:18

在这里插入图片描述

当前时间:2077年,一个阴雨连绵的周二 地点:Neo-Cupertino,第 42 区,“无限循环” 咖啡馆 人物

  • Jet:资深架构师,义眼闪烁着蓝光的代码老兵,热衷于复古的 Swift 语法。
  • Nova:刚入行的初级工程师,满脑子是最新的神经链接框架,但经常被编译器暴揍。
  • 反派“The Race Condition” (竞态幽灵),一个游荡在系统内存缝隙中的古老 AI 病毒,专门吞噬不安全的变量。

窗外的霓虹灯光透过雨幕,在 Jet 的合成皮风衣上投下斑驳的阴影。他抿了一口手中的高浓度咖啡因液,看着面前焦头烂额的 Nova。

“我的编译器又在尖叫了,” Nova 把全息屏幕推向 Jet,上面红色的错误提示像鲜血一样流淌,“我只是想在一个 Actor 里用个简单的类,结果 Swift 的并发检查像个疯狗一样咬着我不放。我是不是该把所有东西都加上 @MainActor 算了?”

在这里插入图片描述

Jet 叹了口气,那是见惯了无数次堆栈溢出后的沧桑。“滥用主线程隔离?那是饮鸩止渴。Nova,你被恐惧蒙蔽了双眼。来,看看这份 2025 年的加密文档。那时候我们管这个叫——Non-Sendable First Design。”

在本篇博文中,您将学到如下内容:

  • 🦾 序章:尴尬却必要的兴奋
  • 🧠 核心概念重载 (Quick Refresher)
  • 🔒 困兽之斗?不,是绝对领域 (Getting Stuck)
  • ⚖️ 权衡:光与影 (The Pros and Cons)
    • 💎 优势 #1:极致的简单 (Simplicity)
    • 🌐 优势 #2:通用性 (Generality)
    • 🕳️ 弱点 #1:启动任务的陷阱 (Starting Tasks)
  • 💊 并不完美的解药
  • 🛑 启动任务本身就是一种充满危险的诱惑
    • 🕸️ 弱点 #2:诡异的属性 (Weird Properties)
  • 🎬 终章:回归本源 (First for a Reason)

Jet 手指轻挥,将文档投影在两人中间,开始了他的解构。

在这里插入图片描述


🦾 序章:尴尬却必要的兴奋

Jet 指着文档的开头说道:“这作者是个老实人。他承认自己对 Non-Sendable Types (非跨域传输类型) 的痴迷程度简直到了走火入魔的地步,甚至有点尴尬。”

“这就好比给自己的战术命名一样,听起来有点自以为是。但他把这种设计理念称为 ‘Non-Sendable First Design’。虽然名字听起来像是什么二流黑客的代号,但其核心思想在当年可是振聋发聩。”

在这里插入图片描述

“在那个年代,因为语言特性的限制,大家对 Non-Sendable 类型避之唯恐不及。它们虽然有用,但用起来就像是在满是碎玻璃的地上跳舞——人体工程学极差。直到有一天,NonisolatedNonsendingByDefault(即 Swift 6 中的‘可接近并发’特性)横空出世。这一切都变了!Non-Sendable 类型突然变得顺滑无比,仿佛这才是它们原本的宿命。”


🧠 核心概念重载 (Quick Refresher)

“在深入之前,我们要先进行一次思维格式化。” Jet 的义眼转动,调出了基础理论图谱。

Swift 解决 Data Race (数据竞争) 的手段非常硬核:它要求在编译时就确立对非线程安全数据的保护。这种抽象被称为 Isolation (隔离)

  • 实现层:可能是锁 (Lock)、队列 (Queue),或者是专用的线程。
  • 运行时:由 Actor 负责具体的保护机制。

“编译器其实是个‘脸盲’,” Jet 解释道,“它不知道某个 Actor 到底是怎么运作的(比如 MainActor 其实是把任务倒进主线程这个大漏斗里),它只知道一点:Actor 能护犊子。”

在这里插入图片描述

Swift 将数据世界一分为二:

  1. Sendable (可传输类型):天生的战士,线程安全,可以在并发的枪林弹雨中随意穿梭,无需保护。
  2. Non-Sendable (非可传输类型):共享的可变状态。它们是我们程序中最有趣、最核心的部分,比如你的用户数据、缓存状态。但它们也是脆弱的,就像没有穿护甲的平民,必须被保护。

“很多人误以为 Actor 只是用来‘后台运行’的工具,这完全是买椟还珠。Actor 的真正使命,是充当 Non-Sendable 数据的保镖,防止它们被竞态幽灵吞噬。”


🔒 困兽之斗?不,是绝对领域 (Getting Stuck)

“听着,Nova。世界上最好的安保系统,如果不允许任何人进出,那也没用。但在 Swift Concurrency 的法则里,有一个非常有趣的特性:”

如果一个 Actor 拥有(通常是创建)了一个 Non-Sendable 类型,这个类型就被‘困’住了。

“想象一下,” Jet 描绘道,“一个由类和协议组成的庞大网络,彼此交织,协同工作。它们可以在 Actor 的围墙内为所欲为。但编译器这个冷酷的守门人,绝对禁止你不安全地将它们扔到墙外。”

在这里插入图片描述

“这听起来像是限制,但这正是 Non-Sendable 类型的强大之处——画地为牢,反而成就了绝对的安全。”


⚖️ 权衡:光与影 (The Pros and Cons)

Jet 调出了对比数据面板。“作者曾纠结于如何展示这些,最后他决定返璞归真,先给你看甜头,再告诉你陷阱。注意,这里默认的语境是 nonisolated (非隔离) 的。”

💎 优势 #1:极致的简单 (Simplicity)

“看这段代码,Nova。它简单得像是一首儿歌。”

class Counter {
    var state = 0
    
    func reset() {
        self.state = 0
    }
}

“这就是一个普通的类,甚至还有可变状态。再看这一步:”

class Counter {
    // ...

    // 这里加上了 async
    func toggle() async {
        self.state += 1
    }
}

extension Counter: Equatable {
    static func == (lhs: Counter, rhs: Counter) -> Bool {
        lhs.state == rhs.state
    }
}

Jet 敲着桌子强调:“这里有两个关键点!”

  1. Async 方法:在旧时代,nonisolated + async 意味着代码会跑去后台线程,但这对于 Non-Sendable 类型来说是个悖论(它不能离开 Actor 的保护)。这曾经是个第22条军规式的死锁。但现在,有了 NonisolatedNonsendingByDefault,这个问题迎刃而解。
  2. 协议一致性 (Protocol Conformance):看那个 Equatable。这对于普通类来说易如反掌。但如果是 MainActor 隔离的类型?那简直是噩梦,你得处理各种隔离上下文的匹配问题。

Non-Sendable 类型拥有隔离类型所不具备的纯粹和简单。

在这里插入图片描述

🌐 优势 #2:通用性 (Generality)

“这需要一点悟性,” Jet 眯起眼睛,“这关乎同步访问 (Synchronous Access)。”

“如果你给类型加上了 @MainActor,那只有主线程的朋友才能同步访问它。这就像是个 VIP 俱乐部。但 Non-Sendable 类型是通用的雇佣兵。”

actor ActorClient {
    // ActorClient 拥有这个 counter
    private let counter = Counter()
    
    func accessSynchronously() {
        // ✅ 这是完全可能的!
        // 因为 counter 是 Non-Sendable,它被"困"在了 ActorClient 的隔离域内,
        // 所以 ActorClient 可以像操作自家后院一样同步操作它。
        counter.reset() 
    }
}

“它的接口保持不变,不管是谁‘拥有’它。这就是海纳百川的通用性。”

在这里插入图片描述


🕳️ 弱点 #1:启动任务的陷阱 (Starting Tasks)

此时,全息投影变成了警告的红色。

“这就是那个让我开始反思的地方,” Jet 指着下面的代码,“也是新手最容易踩的雷区。”

class Counter {
    // ...

    func printStateEventually() {
        Task {
            // ❌ 错误: 将闭包作为 'sending' 参数传递...
            // 编译器会阻止你,因为你试图在新的 Task 里捕获非 Sendable 的 self
            print(self.state)
        }
    }
}

“为什么不编译?把它翻译成古老的 GCD 你就懂了:”

// ⚠️ 警告: 在 @Sendable 闭包中捕获了 non-Sendable 类型 'Counter'
DispatchQueue.global().async {
    print(self.state)
}

“这就像你在没有任何保护措施的情况下,试图把一个易碎的花瓶扔到正在高速运转的传送带上。在一个 nonisolated 的函数里启动 Task,意味着上下文是不确定的。没有线程安全的保证,编译器绝不会放行。

在这里插入图片描述

💊 并不完美的解药

“为了解决这个问题,我们要么回到原来的队列(像 GCD 那样),要么把 isolation 参数传进去。”

class Counter {
    // 显式传递隔离上下文,这代码丑得像被辐射过的变异体
    func printStateEventually(isolation: isolated any Actor) {
        Task {
            _ = isolation // 这里的魔法是为了继承隔离域
            print(self.state)
        }
    }
}

“这方案有毒,” Jet 摇摇头,“第一,它有传染性,调用者必须提供 Actor。第二,这语法太啰嗦。最重要的是……”

在这里插入图片描述

🛑 启动任务本身就是一种充满危险的诱惑

“Jet 突然严肃起来:“有人曾告诉我,不应该在这种类型内部创建非结构化的 Task。以前我觉得那是废话,现在我觉得那是金玉良言。”

“在类型内部悄悄启动一个外部无法等待 (await) 的任务,这是埋雷。这让代码变得难以观测、难以测试。这其实是一种语法盐 (Syntactic Salt)——语言故意让你难受,是为了告诉你:别这么干!

“正确的做法是使用 async 方法,保持在结构化并发的世界里。如果非要开新任务,让调用者去开。”

在这里插入图片描述


🕸️ 弱点 #2:诡异的属性 (Weird Properties)

“还有一些边缘情况,” Jet 快速带过,“比如 lazy var、属性包装器 (Property Wrappers) 或者宏 (Macros)。如果在这些东西里混合复杂的并发要求,你会发现自己进退维谷。”

class Counter {
    // 这种延迟加载在并发环境下可能极其复杂
    lazy var internal = {
        SpecialCounter(isolatedParam: ???) 
    }()
}

“这虽然罕见,但一旦遇到,就像是撞上了隐形墙。前车之鉴,不可不防。”

在这里插入图片描述


🎬 终章:回归本源 (First for a Reason)

雨渐渐停了,Neo-Cupertino 的黎明即将来临。Jet 关闭了全息投影,看着若有所思的 Nova。

“‘Non-Sendable First’,不是让你永远只用这一招,而是把它作为起点。”

在这里插入图片描述

“在 Swift 5.5 之前,所有东西本质上都是 nonisolated 的。这才是‘常态’。只要不涉及跨线程,它们就是最轻量、最高效的选择。”

“当然,当你遇到真正的并发需求,当你发现无法在 Actor 之间安全传递数据时,再考虑加上 SendableActor 约束。但在那之前……” Jet 站起身,整理了一下衣领。

不要为了并不存在的并发问题,去背负隔离带来的沉重枷锁。 利用 Region Isolation (区域隔离) 的特性,让编译器帮你推导安全性。这种感觉就像是作弊,但却是合法的。”

在这里插入图片描述

Nova 看着屏幕上终于变绿的编译通过提示,眼中闪过一丝光芒。“所以,大道至简?”

“没错,” Jet 转身走进晨雾中,留下最后一句话,“如果觉得复杂,说明你走错了路。Non-Sendable 的世界,简单得让人上瘾。”

在这里插入图片描述

赛博深渊(上):用 Apple Foundation Models 提炼“禁忌知识”的求生指南

2026年1月29日 10:16

在这里插入图片描述

🍎 引子

新九龙城的雨从未停过。霓虹灯的废气在湿漉漉的街道上晕染开来,像极了那个死于代码过载的倒霉蛋老王流出的脑浆。

在贫民窟第 404 区的一间昏暗安全屋里,一名代号为“老 K”的黑客正对着一块发着幽蓝光芒的屏幕,手指在键盘上敲击出残影。墙角的服务器风扇发出濒死的哀鸣,仿佛随时都会起飞爆炸。

在这里插入图片描述

在这个被巨型企业“果核公司(Fruit Corp)”统治的赛博世界里,数据就是生命,而算力就是货币。

老 K 刚刚从公司的主机里偷出了一份代号为“创世纪”的加密文本,文件大得惊人,如果不赶紧在本地进行 “摘要提炼”,追踪程序(那些被称为“猎犬”的 AI)顺着网线摸过来,把他那可怜的脑机接口烧成灰烬只是时间问题。

在本次冒险中,您将学到如下内容:

  • 🍎 引子
  • 🧠 缩水的“大脑”与本地化的艺术
  • 🛠️ 装备检查:不要试图在烤面包机上运行核程序
  • 🕵️‍♂️ 侦测可用性:敌我识别系统
  • 💉 注入代码:瞒天过海
  • 🎭 模拟测试:在母体中演练
  • 🎆 尾声:黎明前的微光

但他不能上传到云端处理。云端是“它们”的地盘。他必须在本地,用那个刚刚解禁的传说级武器——Apple Foundation Models


🧠 缩水的“大脑”与本地化的艺术

“听着,菜鸟,”老 K 转过头,仿佛打破了第四面墙,对着作为学徒的你说道,“想要活命,就得学会如何在端侧(On-device)跑大模型。”

Apple Foundation Models 提供了一种能在用户终端设备(不管是你的义体植入终端,还是手里的 iPhone/Mac)上本地运行的大语言模型(LLM)

在这里插入图片描述

你要知道,传统的 LLM 就像是住在数据中心里的巨型怪兽,吃的是高功率 GPU,喝的是海量的显存和电力。想要把这种怪兽塞进你的口袋里,不仅需要勇气,还需要黑科技。

这里有两个关键的 “瘦身” 魔法:

  1. 参数削减 (Reducing Parameters):把脑子里那些没用的神经元切掉,只保留核心逻辑。
  2. 模型量化 (Quantizing Model Values):如果说原来每个神经元需要 32 位浮点数的高精度,现在我们把它压缩到 4 位或 8 位。虽然精度略有下降,但体积小到可以忽略不计。

在这里插入图片描述

⚠️ 风险提示:这些被“阉割”过的模型依然保留了 LLM 的通病——幻觉 (Hallucinations)。它可能会一本正经地胡说八道,就像喝了假酒的算命先生。但在提炼摘要、理解文本、甚至简单的数据生成任务上,它依然是把好手。

好了,废话少说,让我们开始改装你的代码,让它能驾驭这股力量。


🛠️ 装备检查:不要试图在烤面包机上运行核程序

想要驾驭 Apple Intelligence,你的硬件得跟得上。

“别拿你那台老古董来丢人现眼。”老 K 吐了一口烟圈。

硬性指标:

  • 操作系统:必须是 macOS 26.0 或更高版本(对于 iOS 设备,则是 iOS 26/iPadOS 26)。
  • 硬件支持:设备必须原生支持 Apple Intelligence(那种山寨的义体插件不仅没用,还会炸)。
  • 开关状态:用户必须在设置里手动开启了 Apple Intelligence。

在这里插入图片描述

☠️ 避坑指南:如果你在 macOS 26 的虚拟机里折腾,大概率会翻车。Apple Foundation Models 对虚拟化环境过敏。找台真机,或者支持该特性的物理设备。

在这里插入图片描述


🕵️‍♂️ 侦测可用性:敌我识别系统

我们的目标是那个 Share Extension(分享扩展)。我们要让它变成一个能吞噬长文本并吐出精华摘要的黑洞。

在这里插入图片描述

现在的 App 像个傻子,只会把你选中的文字原样复读一遍。我们要改造它。

首先,在 SummarizeExtension 文件夹下创建一个新的 SwiftUI 视图,命名为 ModelCheckView.swift。这就像是我们的看门狗,用来检测当前环境是否安全。

在这里插入图片描述

在该文件顶部引入这一行禁忌的咒语:

import FoundationModels // 引入 Apple 的本地模型库

然后在结构体里加入这些属性:

// 这里的 sharedText 是我们要处理的“赃物”(文本)
let sharedText: String
// 任务完成后的回调,毕竟我们得知道什么时候跑路
let onDone: () -> Void
// 核心:获取系统默认的语言模型实例
let model = SystemLanguageModel.default 

接下来,把原本的 body 替换成这一段充满求生欲的代码:

// 1. 检查模型的可用性状态
switch model.availability {

// 2. 状态:可用。谢天谢地,我们可以干活了
case .available:
  // 这里调用真正干活的摘要视图(后面我们会讲)
  SummaryView(sharedText: sharedText, onDone: onDone)

// 3. 各种不可用的“死法”
case .unavailable(.deviceNotEligible):
  Text("⚠️ 你的设备太老了,跑不动 Apple Intelligence。")
  
case .unavailable(.appleIntelligenceNotEnabled):
  Text("🛑 Apple Intelligence 虽已就绪,但你还没打开开关。快去设置!")
  
case .unavailable(.modelNotReady):
  Text("⏳ 模型还没热身完毕,稍后再试。") 
  // 注意:在模拟器里看到这个,通常意味着你的宿主机不支持,或者你在虚拟机里套娃。

// 4. 未知错误,最可怕的一种
case .unavailable:
  Text("👾 未知错误阻止了 Apple Intelligence 的运行。可能是赛博幽灵作祟。")
}

在这里插入图片描述

老 K 的技术旁白

  • model.availability:这是你的盖革计数器。一定要先 switch 它,不然直接调用模型会导致程序崩溃,就像在没有氧气的地方点火一样。
  • unavailable 的各种姿势:一定要给用户(或者你自己)清晰的反馈。与其让 App 闪退,不如告诉他“你的装备不行”。

在这里插入图片描述


💉 注入代码:瞒天过海

现在,我们要把这个检测机制植入到 ShareViewController.swift 里。这个文件是连接古老的 UIKit 世界和新锐 SwiftUI 世界的桥梁。

找到 showSwiftUIView(with:) 方法,我们要玩一招“偷梁换柱”。

// 用我们新的 ModelCheckView 替换掉原来的直接调用
let wvc = UIHostingController(
  rootView: ModelCheckView(
    sharedText: text,
    onDone: closeExtension // 事情办完就销毁现场
  )
)

这样,当你在 Safari 里选中一段长得令人发指的文本,点击“分享 -> LocalSummarizer”时,系统会先经过我们的 ModelCheckView 查岗。

在这里插入图片描述


🎭 模拟测试:在母体中演练

“别急着上线,现在的网络全是眼线。”老 K 按住你的手,“先在 Xcode 的模拟环境中跑一遍。”

即使你的设备牛逼哄哄,你也得测试一下如果用户没开功能会怎样。

在这里插入图片描述

  1. 在 Xcode 顶部选择 SummarizeExtension 方案。
  2. 点击 Edit Scheme...
  3. Run -> Options 标签页下,找到 Simulated Foundation Models Availability

在这里插入图片描述

这就是你的上帝模式开关。你可以把它设为:

  • Apple Intelligence Not Enabled:看看你的 App 会不会乖乖提示用户去开启。
  • Off:这是默认值,反映你真实设备的状态。

在这里插入图片描述

⚠️ 警告:测完记得把它改回 Off。别到时候明明设备支持,却因为这里没改回来而对着屏幕怀疑人生,那种感觉就像是找了半天眼镜结果眼镜架在鼻子上一样蠢。

在这里插入图片描述

为了让主 App (LocalSummarizer) 也能自检,我们顺手改一下 ContentView.swift

import FoundationModels

// ... 在 body 里 ...
switch SystemLanguageModel.default.availability {
case .available:
  Text("✅ 此设备已准备好接入 Apple Foundation Models。")
// ... 其他 case 照抄上面的 ...
}

在这里插入图片描述


🎆 尾声:黎明前的微光

老 K 敲下最后一个回车键。屏幕闪烁了一下,那段数百万字的冗长文本,瞬间被 Apple Foundation Models 压缩成了寥寥几百字的精华摘要。

没有联网,没有上传,一切都在悄无声息中完成。

在这里插入图片描述

“搞定了。”老 K 拔掉数据线,嘴角露出一丝不易察觉的微笑,“猎犬们还在云端嗅探我们的踪迹,殊不知我们已经带着情报,大摇大摆地从它们眼皮子底下溜走了。”

在这里插入图片描述

窗外的雨似乎小了一些。在这个被数据淹没的时代,掌握本地大模型技术,不仅仅是为了开发几个花哨的功能,更是为了在这个监视无处不在的赛博丛林里,保留最后一份隐私和自由。

在这里插入图片描述

你看着手里显示着 "Summary Generated" 的屏幕,深知这只是开始。下一章,我们将利用这股力量,去触碰更深层的禁忌……

(未完待续...)

理财学习笔记(2):不懂不投

作者 唐巧
2026年1月29日 09:51

这是本系列的第 2 篇,主题是:不懂不投。

我们刚开始投资理财的时候,通常会寻求以下这些方法来找到投资标的。

常见的错误办法

1、问朋友。我们通常会问那些看起来投资理财收益比较高的朋友,问他们应该买什么股票。
对于朋友推荐的股票,我们通常会“无脑”买入。但如果有一天,股票突然大幅回撤,我们通常就会陷入恐慌。我们会怀疑:这个朋友到底靠不靠谱?他之前赚钱是靠运气,还是因为现在判断出了问题?接着,我们就会陷入各种猜忌、焦虑和紧张中,最后甚至睡不着觉。如果股票持续下跌,我们甚至可能割肉离场。所以说,跟着朋友买其实并不那么靠谱。

2、看走势。我们可能会去看某些股票或基金的历史走势。看到它在过去三年或五年涨得很好,我们就买入。这也是理财 App 或者某些理财经理推荐的首选理由:它过去 X 年涨幅 XX,排名 XX。

但这很容易陷入“价值陷阱”,比如:

  1. 周期性误判:有些股票仅仅是在某个周期内表现优秀。比如房地产在过去十年涨得很好,但这并非因为单体公司有多好,而是因为当时整个大环境让所有房企都很赚钱。如果你仅仅因为过去业绩好而买入,一旦遭遇经济下滑或泡沫破裂,就会面临巨大的损失。

  2. 均值回归陷阱:很多股票或基金某年表现出色,仅仅是因为那一年的风格与它匹配。所有行业都有“大小年”之分,未来遇到“小年”时,表现自然就会变差。我把这叫做“均值回归”。

这就好比考试:你的平均水平可能是第三名。发挥好的时候能考第一名,发挥不好则可能掉到第五名,但你始终是在第三名上下徘徊。

很多基金经理或股票的表现也是在自身价值上下震荡。如果你在高点买入,在回撤时就会损失惨重,甚至被深套。

3、跟风。跟风是 A 股散户的常见操作,某个时间什么热,就跟风买什么,涨了就快速卖掉,主打一个击鼓传花,赌谁是最后接盘的大傻子。

这种情况下,我们假设你的胜率是 50%。每次获胜挣 20%,每次赌失败亏 20%。如果你进行了 10 次这样的操作,那你整体的收益期望就是 (1.2^5)*(0.8^5)=0.82,所以你折腾了半天,最后 1 块钱的本金变成了 0.82 元。

当然,如果有人认为自己跟风总是赢,这也是有可能的,但是因为自己不敢长期持有,只要涨一点点就卖,其实每次挣的是一点点收益。但是如果偶尔遇到亏损的时候,自己舍不得卖掉,就会一次亏很多。做这种短线操作的人,需要极强的止损纪律,大部分人也是很难做到的。

不懂不投

所以回到股票投资,我觉得投资理财一定要自己懂才行。如果你完全不懂或一知半解,这些都会成为你的陷阱。因为:

  1. 心理层面:不懂的人往往“拿不住”。当股票大幅下跌时,无论是否割肉,你都会极度焦虑、睡不好觉,担心本金损失。
  2. 投资层面:如果你懂,面对下跌说不定还能逆势加仓;即便不加仓,至少能睡个好觉。

此外,世界上还有很多投资陷阱。有些人甚至专门为“制造陷阱”而生,比如搞资金盘、割韭菜或传销。这些行为有些是非法的,有些则游走在法律边缘。如果大家没有能力分辨这些陷阱,很容易就在投资理财中遭遇严重的亏损。

小结

小结一下,常见的错误投资:

  • 问朋友。其实本质上信的是朋友的业绩,朋友如果业绩下滑,就会怀疑。
  • 看走势。其实本质上是用过去业绩替代未来判断,不靠谱。
  • 跟风。纯投机,50% 胜率下期望是负的。

心理层面,只有懂了,才可能拿得住,睡得着觉。

另外,真正懂也可以避免很多骗局。

以上。

NSProcessInfoThermalState 的作用

作者 iOS在入门
2026年1月28日 22:59

借助AI辅助。

NSProcessInfoThermalState 的作用

NSProcessInfoThermalState 是一个用于表示设备散热状态的枚举类型。它帮助应用程序了解设备当前的温度水平,从而采取适当的措施来防止设备过热。

枚举值及含义

这个枚举包含四个级别:

  1. Nominal(正常) - 设备温度处于可接受水平,对用户无负面影响

  2. Fair(一般) - 设备温度略有升高,风扇可能开始工作并发出声音

  3. Serious(严重) - 设备温度明显升高,风扇全速运转,系统性能可能受到影响

  4. Critical(临界) - 设备温度显著升高,设备需要冷却降温

使用方式

// 获取当前散热状态
let thermalState = ProcessInfo.processInfo.thermalState

// 监听散热状态变化通知
NotificationCenter.default.addObserver(
    forName: ProcessInfo.thermalStateDidChangeNotification,
    object: nil,
    queue: nil
) { notification in
    // 处理散热状态变化
}

应用场景

当散热状态升高时,应用程序应该采取措施降低系统负载:

  • 减少 CPU/GPU 使用率
  • 降低帧率
  • 减少 I/O 操作
  • 使用低质量的视觉效果
  • 暂停后台任务

这对于保护设备硬件、提升用户体验(避免设备过热导致的性能下降或风扇噪音)非常重要,特别是在进行密集计算或图形渲染的应用中。

昨天 — 2026年1月28日iOS

3-27.【函数式编程】讨论 Combine 的 Publisher 如何体现 Functor / Monad 特性。

作者 项阿丑
2026年1月28日 17:00

1️⃣ Functor 特性在 Publisher 中的体现

Functor 核心:能够把函数映射到容器/上下文中的值,同时保持结构不变。

在 Combine 中:

  • Publisher 可以看作一个 容器,异步发出值
  • 使用 map 可以对 Publisher 发出的每个元素进行函数映射
  • 不改变 Publisher 的结构,只改变输出值

示例

import Combine
import Foundation

let publisher = [1, 2, 3].publisher // ArrayPublisher<Int>

let mapped = publisher.map { $0 * 2 }

let cancellable = mapped.sink { value in
    print("Mapped value:", value)
}
// 输出:Mapped value: 2
//       Mapped value: 4
//       Mapped value: 6
  • map 只改变 输出值,不改变 Publisher 的类型
  • 多次 map 可组合,满足 Functor 规律:
let f: (Int) -> Int = { $0 + 1 }
let g: (Int) -> Int = { $0 * 10 }

let left = publisher.map(f).map(g)
let right = publisher.map { g(f($0)) }
  • left == right ✅ 组合律满足

2️⃣ Monad 特性在 Publisher 中的体现

Monad 核心:支持 bind(flatMap)操作,能够把返回容器的函数平铺,避免嵌套容器。

在 Combine 中:

  • flatMap 对应 Monad 的 bind
  • 输入:(Output) -> Publisher<NewOutput, Failure>
  • 输出:单一 Publisher,把可能产生的多个嵌套 Publisher 平铺
  • 支持链式组合多个异步操作

示例

假设我们有两个异步 Publisher 函数:

func fetchNumber(_ id: Int) -> AnyPublisher<Int, Never> {
    Just(id * 2).eraseToAnyPublisher()
}

func fetchString(_ number: Int) -> AnyPublisher<String, Never> {
    Just("Number is (number)").eraseToAnyPublisher()
}

// 使用 flatMap 链式组合
let cancellable2 = [1, 2, 3].publisher
    .flatMap { fetchNumber($0) }   // Monad bind,平铺 Publisher
    .flatMap { fetchString($0) }
    .sink { print($0) }

解释

  1. [1,2,3].publisher → Publisher

  2. .flatMap(fetchNumber) → Publisher

    • 每个值会生成一个新的 Publisher
    • flatMap 平铺输出,而不是产生嵌套 Publisher<Publisher>
  3. .flatMap(fetchString) → Publisher

  4. 最终输出一个 扁平 Publisher,可以订阅


3️⃣ Functor vs Monad 对比在 Combine 中

特性 Functor (map) Monad (flatMap)
操作类型 (Output) -> NewOutput (Output) -> Publisher<NewOutput, Failure>
结构变化 不改变 Publisher 层级 平铺嵌套 Publisher
适用场景 对值做同步转换 异步链式调用,生成新的 Publisher
示例 .map { $0 * 2 } .flatMap { fetchNumber($0) }

4️⃣ 总结

  1. Functor

    • Publisher.map → 映射输出值,保持 Publisher 结构
    • 遵循恒等和组合律
  2. Monad

    • Publisher.flatMap → 绑定新的 Publisher,平铺嵌套,短路失败可处理
    • 支持函数式异步流水线,链式组合

💡 核心思想

Combine 的 Publisher 就是一个 异步容器,Functor 和 Monad 的特性保证了:

  1. 可以安全映射数据 (map)
  2. 可以安全组合异步操作 (flatMap)
  3. 异步值处理保持函数式风格,副作用集中在 sinkassign

我的笔记系统

2026年1月28日 08:00

笔记大概分为三类:个人相关、工作相关和知识相关。个人向的主体是「我」,通常只对自己有意义;工作向的笔记自然与工作相关;知识向的笔记则致力于形成知识网络,时效性较长,也是本文讨论的重点。

相信大家都有用过大语言模型(LLM),如 ChatGPT,DeepSeek,豆包,Gemini 等等,给一个问题,就能得到不错的答案,那么在大语言模型不断进化,AI 工具愈发强大的当下,是否还有记笔记的必要?我认为:不仅有必要,而且比以往更重要。 但前提是,我们需要重新定义「记笔记」这件事。

笔记是什么?我把它看作 「外化的思考脚手架」。我们的大脑工作内存有限,只能同时处理 3-5 个想法,笔记可以将大脑从「记忆」的负担中解放出来,全力投入到「运算」中。笔记不是最终的目的,而是用于构建更高层建筑的工具,比如写文章,做决策,解决问题,它的价值在于它能支撑你爬得更高。

更形象的比喻或许是:预处理过的「半成品料理包」。当你来到厨房(需要解决问题/写作/决策)时,不需要从洗菜、切菜开始,而是直接拿出切好的配菜、调好的酱汁,就能快速烹饪出一道大餐。

在 AI 时代,有什么不懂直接问 AI 就好了,为什么还要记笔记?因为缺少内化的知识网络,就问不出好问题,没有好问题,就很难得到好答案,就无法最大程度地挖掘 AI 的潜力。大语言模型遵循的是 GIGO(Garbage In Garbage Out)原则,没有好的输入,就很难得到好的输出。笔记系统可以帮助我们构建/强化知识网络,从而问出好问题。

比如前一阵很火的 Dan Koe 的 How to fix your entire life in 1 day 这篇文章,看完之后,可能觉得很有道理,但不一定能问出合适的 follow up,比如文章提到的思想跟斯多葛的消极想象有什么联系?或文章提到的身份认同理论是否与 Atomic Habits 中提到的身份认同概念一致?以这些问题为切入点,可能又能获得到一些不错的新的知识点和看世界的角度,进而丰富自己的知识体系。

工作流概览

一个好的笔记系统不仅仅是工具的堆砌,更是信息的流动。我的工作流包含五个阶段:

  1. 捕获:极低阻力地快速收集。
  2. 存储:将待处理内容归位到合适的介质。
  3. 处理:提炼、消化原始内容。
  4. 回顾:建立连接,内化知识。
  5. 产出:用笔记解决实际问题,形成闭环。

一、捕获阶段 (Capture)

核心原则:极低阻力。灵感和信息稍纵即逝。这个阶段唯一的任务就是把脑子里的想法或外界的信息扔进一个固定的盒子里。此刻不要整理,也不要分类,只要丢进去即可。

我推荐 Apple Notes 的 Quick Note,系统级集成,很方便。Mac 上一键唤出,iPhone Control Center 随时点击。支持富媒体(语音、手绘、链接),就像一张随手可得的便利贴。

我的信息主要来自 Twitter(X)、YouTube、Newsletter、博客以及与 Gemini 的对话。为了解决「想看视频但没时间/效率低」的问题,我还构建了一套自动化流程:用 js 脚本调用 YouTube API 抓取字幕,通过 LLM 进行精简并整理成文章,最后打包成 Epub 电子书。这让我能像阅读文章一样「阅读」视频,大大提升了效率。

Gallery image 1

这里要避免沉迷于「寻找好内容」这种多巴胺陷阱,建议设定特定的「进货时间」(如周末早晨),批量获取信息,然后断连。同时不要试图在捕获阶段去消化内容,那样会打断「狩猎」节奏。

二、存储阶段 (Storage)

捕获的内容通常是链接、书籍或长文。这个阶段的目标是让它们「各归其位」,等待处理。

「链接」我推荐 Goodlinks。它没有订阅制,设计优雅,功能纯粹。我把它当作我的链接「中转站」。

「电子书」我没有使用 Apple Books 或 Calibre,而是直接使用 macOS Finder + Tags。把待看的书扔进文件夹,看完的书打上特定的标签,这样只要 filter by tag,就能看到看过的书和没看的书。这么做的一个原因是不争气的 Apple Books,它不支持 Smart Filter,只能手动创建 Collection,这样就很不方便筛选出没有看的书,我希望它像一个 Queue 或 Stack,随着书一本一本被看完,这个列表里的内容也会逐渐减少。还有一个原因是,书放进去后,再导出来也不太方便。

三、处理阶段 (Processing)

这是整个工作流中最重要,也是最容易被忽视的部分,很多人的笔记系统往往停在了上一步。这一步的目的是蒸馏(Distillation),提炼出有价值的内容,而不是简单地复制粘贴。

这个阶段最重要的,也是最难的部分,是要为它留出时间(比如每天晚上),因为做这件事可能没有那么愉悦,如果不专门留时间,几乎肯定会被其他阻力更小的事情代替。

这个阶段我用到的工具是 Dia 浏览器,没有直接在仓库中处理是不想看着一大堆未处理的内容产生焦虑,选择 Dia 浏览器是因为它的 Vertical Sidebar 和 Split view 很方便,同时因为它是浏览器,对链接天然友好,还能方便地唤出 Gemini。

浏览器可以打开 pdf,但默认不支持 epub,所以我又做了一个浏览器的 epub 插件,可以一边看书,一边与 Gemini 就书的内容进行交流。

Gallery image 1

待处理的内容通常比较长,或者是非母语的内容,为了提高效率,我会先让 Gemini 对内容进行压缩,如果感兴趣,再去看原文,然后与 Gemini 就里面的内容进行深度的交流。这是一个例子。交流完后,通常会有这些产出:

  • 一篇原文的精简版(放到笔记 App 里)
  • 一篇讨论后的笔记(放到笔记 App 里)
  • 一些原文的精彩摘录(放到笔记 App 里)
  • 方便录入到 Anki 的卡片(整理成实体卡片)
  • 相关推荐

Anki 相关的 App 一直用不起来,还是更喜欢实体的卡片,所以会把相关的知识点写到卡片上,顺便加深下印象。

Gallery image 1

处理后的笔记,我选择存放在 Bear 中。

  • 为什么不选 Obsidian? 它确实功能强大且免费,也有丰富的插件系统,但我用起来总觉得不够「舒服」。
  • 为什么不选 Apple Notes?它对 Markdown 的支持不友好,内容也有点封闭,写作体验也不如 Bear。

选择 Bear 还有一个好处,它的笔记可以很方便地导出为 Markdown,方便二次加工和后续迁移。孤立的笔记是死的。让笔记活过来的关键是Link(链接)。因为 Bear 的笔记都存在本地的一个 SQLite 数据库里,所以可以很方便地读取和处理。我写了一个 js 脚本,将 Bear 里的笔记内容向量化(Vectorization),然后计算余弦相似度,自动生成「相关笔记」列表。

四、回顾阶段 (Review)

把笔记存进去如果不看,那意义也不大。为了方便回顾,我做了一个 Web App(notes.limboy.me),每次随机展示一篇笔记作为起点,然后通过「相关笔记」进行漫游。同时也会在碎片时间把上一个阶段生成的卡片拿出来翻一翻,加深印象。

Gallery image 1

五、产出阶段 (Output)

笔记不是目的,它是为了帮助生成洞见(Insight)、新的看事物的角度和强化知识网络而存在,最好的方式就是输出,比如写文章、做分享、做决策等。以写文章为例,如果想写一篇关于「习惯养成」的文章,不再是面对空白文档抓耳挠腮,只需在笔记库里搜索「习惯」、「行为心理学」,把相关的 5-6 个笔记块(料理包)调出来,重新排列组合,加上新的连接词,文章的 80% 就完成了。

结语

如果没有一套运行顺畅的笔记系统,没有为消化笔记专门留出时间,没有输出的压力,那么笔记的价值就会大打折扣,再好的工具也无法做到第二大脑。希望这篇文章能给你带来些帮助和启发,如果你有好的想法和经验,也欢迎分享。

2-6.【Concurrency】TaskGroup 的设计目的是什么?它如何保证并发安全?

作者 项阿丑
2026年1月28日 14:11

一句话先给结论

TaskGroup 的设计目的,是在保持结构化并发的前提下,
安全地表达“一组并发执行、统一收敛结果的子任务”。

或者更狠一点的说法:

TaskGroup = “不会失控的 fork–join 并发”。


1️⃣ 为什么需要 TaskGroup?(设计动机)

先看一个“天真实现”的问题:

var results: [Int] = []

Task {
    Task { results.append(await f1()) }
    Task { results.append(await f2()) }
    Task { results.append(await f3()) }
}

❌ 问题一堆:

  • results 有数据竞争
  • 子任务生命周期不受控
  • 取消无法统一传播
  • 无法确定什么时候全部完成

TaskGroup 想解决的正是这些问题

let sum = await withTaskGroup(of: Int.self) { group in
    group.addTask { await f1() }
    group.addTask { await f2() }
    group.addTask { await f3() }

    var total = 0
    for await value in group {
        total += value
    }
    return total
}

2️⃣ TaskGroup 的核心设计目标

🎯 目标 1:结构化并发(Structured Concurrency)

  • 所有子任务:

    • 必须在 group 作用域内完成
    • 不能逃逸
  • 离开 withTaskGroup

    • 要么所有完成
    • 要么全部取消

👉 不会“偷偷活着的任务”


🎯 目标 2:安全地共享结果,而不是共享状态

TaskGroup 的哲学是:

并发任务之间不共享可变状态,只通过结果汇合

  • 子任务:

    • 不写共享变量
    • return
  • 父任务:

    • 串行地消费结果

🎯 目标 3:明确的并发边界

  • 并发只发生在:

    • group.addTask
  • 收敛点是:

    • for await value in group

这让代码在语义上可推理


3️⃣ TaskGroup 的工作模型(非常重要)

逻辑结构

Parent Task
 └─ TaskGroup
     ├─ Child Task 1
     ├─ Child Task 2
     └─ Child Task 3

特点:

  • 子任务都是:

    • 当前 task 的子 task
    • 自动继承取消 / 优先级 / actor

生命周期规则(强约束)

行为 结果
父 task 被取消 group 中所有子任务被取消
任一子任务抛错(throwing group) 其余子任务全部取消
离开作用域 等待所有子任务完成

4️⃣ TaskGroup 如何保证并发安全?

这是你问题的核心 👇


✅ 1. 类型系统层面的隔离

withTaskGroup(of: Int.self) { group in
    group.addTask {
        // 只能返回 Int
    }
}
  • 子任务无法直接访问 group 的内部状态
  • 只能通过 返回值通信

✅ 2. 串行消费结果

for await value in group {
    // 这里是顺序执行的
}
  • 即使子任务并发完成
  • 结果的处理是单线程、顺序的
  • 所以你可以安全地:
var sum = 0
sum += value

✅ 3. 作用域限制(无法逃逸)

你做不到:

var g: TaskGroup<Int>?
withTaskGroup(of: Int.self) { group in
    g = group // ❌ 不允许
}
  • group 不能被存储
  • 不能被跨作用域使用

👉 这从根源上消灭了悬垂并发。


✅ 4. 自动取消传播

  • 父取消 → 子取消
  • 错误 → 全部取消

这避免了:

  • 子任务写了一半共享状态
  • 父任务已经不关心结果

✅ 5. 与 actor 隔离完美配合

actor Store {
    func loadAll() async {
        await withTaskGroup(of: Item.self) { group in
            for id in ids {
                group.addTask {
                    await fetchItem(id)
                }
            }
            for await item in group {
                self.items.append(item) // actor 内,安全
            }
        }
    }
}
  • 子任务不直接修改 actor 状态
  • 所有 mutation 都在 actor 上顺序执行

5️⃣ TaskGroup vs async let(顺便对比)

特性 TaskGroup async let
子任务数量 动态 静态
结果处理 流式 一次性
错误处理 可逐个 一起
适合场景 批量 / 不定量 少量固定

6️⃣ 常见误区(非常常见)

❌ 误区 1:TaskGroup 里共享变量是安全的

var sum = 0
withTaskGroup(of: Int.self) { group in
    group.addTask { sum += 1 } // ❌ 数据竞争
}

子任务仍然是并发的。


❌ 误区 2:TaskGroup 会限制并发数量

不会。
如果你要限流,需要:

withTaskGroup { group in
    // 自己控制 addTask 的节奏
}

或使用 semaphore / AsyncSemaphore。


7️⃣ 终极总结(可以直接背)

TaskGroup 的设计目的,是在结构化并发模型下,
安全地表达“多个并发子任务 + 统一收敛”的计算模式。
它通过作用域约束、结果传递而非状态共享、串行消费、自动取消传播,
从语言和类型系统层面避免了数据竞争和失控并发。


2-5.【Concurrency】Task 和 DetachedTask 有什么区别?使用场景各是什么?

作者 项阿丑
2026年1月28日 14:11

一句话总览

Task {} = 有父任务、会继承上下文的结构化并发
Task.detached {} = 没有父任务、完全独立的非结构化并发


1️⃣ 最重要的区别(先看这个)

维度 Task {} Task.detached {}
是否有父 Task ✅ 有 ❌ 没有
继承取消状态
继承优先级
继承 Task-local
继承 actor / executor
结构化并发
推荐使用 ⭐⭐⭐⭐⭐ ⭐⭐(谨慎)

99% 的情况下,你应该用 Task {}


2️⃣ Task {} 到底是什么?

定义

Task {
    await doSomething()
}

这是一个 child task(子任务)

它会继承什么?

假设你在这里:

@MainActor
func onTap() {
    Task {
        await loadData()
    }
}

这个 Task 会继承:

  • 当前 Task 的取消状态
  • 当前 优先级
  • 当前 Task-local 值
  • 当前 actor(MainActor)

所以:

loadData() // 默认仍在 MainActor

除非你 await 到别的 executor。


为什么这很重要?

因为它保证了:

  • 生命周期清晰
  • 取消可以向下传播
  • 行为可预测

👉 这就是“结构化并发”


3️⃣ Task.detached {} 是什么?

定义

Task.detached {
    await doSomething()
}

这是一个 完全独立的任务

特点(非常关键):

  • ❌ 不属于当前 Task
  • ❌ 不继承 MainActor
  • ❌ 不继承优先级
  • ❌ 不继承取消
  • ❌ 不继承 Task-local

相当于:

“在并发世界里新开了一个孤儿线程(逻辑上)”


4️⃣ 一个最容易踩坑的例子 ⚠️

@MainActor
func onTap() {
    Task.detached {
        updateUI() // ❌ 运行期错误
    }
}

原因:

  • detached task 不在 MainActor
  • 却访问了 MainActor 隔离的状态

必须显式切回:

Task.detached {
    await MainActor.run {
        updateUI()
    }
}

5️⃣ 取消(cancellation)语义差异

Task {}

let task = Task {
    try await longWork()
}

task.cancel()
  • 子任务会被标记为 cancelled
  • await 点会抛 CancellationError
  • 和父任务强关联

Task.detached {}

let task = Task.detached {
    try await longWork()
}
  • 不会被父任务取消
  • 必须手动管理生命周期
  • 很容易“偷偷活很久”

6️⃣ 什么时候该用 Task {}?(几乎所有时候)

✅ UI 事件响应

Button("Load") {
    Task {
        await viewModel.load()
    }
}

✅ actor 内部并发工作

actor Cache {
    func refresh() {
        Task {
            await self.loadFromDisk()
        }
    }
}

✅ 需要自动取消、优先级继承


7️⃣ 什么时候才该用 Task.detached {}

⚠️ 使用条件(缺一不可)

真的需要:

  1. 不继承任何上下文
  2. 生命周期独立于调用者
  3. 手动处理取消 / actor hop

合理场景 1️⃣:全局后台维护任务

Task.detached(priority: .background) {
    await cleanTempFiles()
}
  • 不该被 UI 取消
  • 明确是系统级任务

合理场景 2️⃣:跨 actor / 跨 subsystem 的桥接

Task.detached {
    let data = await fetch()
    await logger.log(data)
}

合理场景 3️⃣:底层库内部(而不是 App 代码)

Swift 官方建议:

Task.detached 主要用于框架 / runtime 层


8️⃣ 官方态度(很明确)

Swift Concurrency 的设计哲学是:

默认结构化并发,尽量消灭 detached task

你可以理解成:

  • Task {} = safe default
  • Task.detached {} = unsafe escape hatch

9️⃣ 快速记忆口诀 🧠

要继承上下文,用 Task
要彻底断亲,用 Task.detached
不确定?别 detached


10️⃣ 最后一段总结(面试版)

Task {} 创建的是结构化的子任务,会继承当前任务的取消、优先级、actor 和 task-local,适合绝大多数并发场景;
Task.detached {} 创建的是非结构化任务,不继承任何上下文,生命周期完全独立,适合极少数系统级或框架级后台工作,普通业务代码应尽量避免。

2-4.【Concurrency】Continuation 是什么?与 async/await 有何关系?

作者 项阿丑
2026年1月28日 14:11

在 Swift(乃至整个并发世界)里:

如果你真正理解了 continuation,你就理解了 async/await 的底层本质。

我会按 概念 → Swift 里的具体形态 → 与 async/await 的关系 → 常见误区 来讲。


一句话定义(先刻在脑子里)

Continuation =「从当前点开始,程序接下来该怎么继续执行」的一个可保存、可传递、可稍后调用的对象。

换句话说:

continuation 就是 “未来的自己”


1️⃣ 直观理解(不用术语)

看这段代码:

let x = await foo()
print(x)

await foo() 这一行:

  • 当前函数不能继续往下跑
  • 但“等 foo 完成之后该干什么”是完全确定的

这段“之后该干什么”的逻辑:

print(x)

👉 这就是一个 continuation


2️⃣ continuation 在 Swift 中到底是什么?

在 Swift Concurrency 里,continuation 不是抽象概念,而是真实存在的东西

在编译器 / runtime 层面

continuation 由以下几部分组成:

  1. resume function

    • 一个函数指针
    • 指向“await 之后的代码块”
  2. async context

    • 保存局部变量、状态机状态
  3. executor 信息

    • 决定在哪里恢复执行

合在一起:

continuation = (async context + resume entry + executor)


3️⃣ async/await 和 continuation 的关系(核心)

❓ async/await 做了什么?

async/await 的本质就是:
把 continuation 自动、隐式地帮你创建和管理了。


不用 async/await(手写 continuation)

func foo(_ cont: @escaping (Int) -> Void) {
    bar { result in
        cont(result + 1)
    }
}

你在手动传递 continuation


用 async/await

func foo() async -> Int {
    let x = await bar()
    return x + 1
}

编译器在背后自动做了:

  • 创建 continuation
  • 保存当前上下文
  • 把 continuation 传给 bar
  • 在合适的 executor 上 resume

👉 你写的是顺序代码,底层仍然是 continuation。


4️⃣ withUnsafeContinuation 是什么角色?

这是 Swift 暴露给用户的 continuation API

func legacy() async -> Int {
    await withCheckedContinuation { cont in
        legacyAPI { value in
            cont.resume(returning: value)
        }
    }
}

这里:

  • cont 就是当前 async 函数的 continuation

  • 调用 resume

    • 就等于“触发 await 后面的代码继续执行”

⚠️ 注意:

  • resume 只能调用一次
  • 否则 continuation 会崩(checked 版会直接 trap)

5️⃣ continuation 与“状态机”的关系

continuation ≠ 状态机
continuation = 状态机的一个“入口点”

  • 状态机:

    • 决定你现在在哪个状态
  • continuation:

    • 是“从这个状态继续跑”的 callable handle

你可以把它理解为:

state machine + continuation = coroutine

6️⃣ continuation 与线程的关系(常见误解)

❌ 错误理解

continuation = 保存当前线程

✅ 正确理解

continuation 不保存线程,只保存“逻辑上的下一步”。

  • 恢复时:

    • 可能在同一个线程
    • 也可能在完全不同的线程
  • 线程由 executor 决定


7️⃣ continuation 在错误、取消中的作用

错误传播

await foo() // throws
  • continuation 包含:

    • normal resume
    • error resume

cancellation

  • task 被取消时

  • runtime 会:

    • 找到 continuation
    • 恢复执行
    • 抛出 CancellationError

8️⃣ 和其他语言的对比(帮助定位)

语言 continuation 表现
Swift 隐式 + 显式(withContinuation)
Kotlin Continuation<T> 参数
JS Promise.then
C++20 coroutine_handle
Scheme call/cc

Swift 的设计目标是:

让 99% 的 continuation 消失在语法糖后面


9️⃣ 终极记忆版总结

Continuation 是“await 之后要做什么”的可执行表示。
async/await 的全部魔法,就是把 continuation 自动保存、传递、恢复。


1-30.【GCD】GCD 如何缓解优先级反转?开发者还能做什么?

作者 项阿丑
2026年1月28日 11:31

1️⃣ GCD 内部缓解机制:QoS 继承

1.1 原理

  • 问题来源

    • 高 QoS 任务等待低 QoS 队列中的任务或锁
    • 如果低 QoS 任务占用线程 → 高 QoS 任务被延迟
    • 典型场景:串行队列 + 低 QoS 在前 + 高 QoS 在后
  • GCD 解决方案

    1. 高 QoS 任务提交到队列时,系统检测 任务阻塞
    2. 临时提升低 QoS 任务到高 QoS
    3. 保证低 QoS 任务尽快完成 → 高 QoS 任务继续执行

1.2 示例

let serialQueue = DispatchQueue(label: "serialQueue", qos: .utility)

serialQueue.async(qos: .background) {
    Thread.sleep(forTimeInterval: 5) // 低 QoS 任务
}

serialQueue.async(qos: .userInitiated) {
    print("高 QoS 任务执行")
}
  • 低 QoS 任务在前,正常会阻塞高 QoS
  • GCD 会临时提升低 QoS 任务到 .userInitiated → 缩短高 QoS 阻塞时间

2️⃣ 开发者可以做的优化

2.1 避免串行队列中高低 QoS 混用

  • 串行队列顺序固定,低 QoS 在前会阻塞高 QoS

  • 建议

    • 高 QoS 任务单独使用高 QoS 队列
    • 低 QoS 任务使用后台队列
let highQueue = DispatchQueue(label: "highQueue", qos: .userInitiated)
let lowQueue = DispatchQueue(label: "lowQueue", qos: .background)

2.2 使用异步调度

  • async 提交任务不会阻塞调用线程
  • 可以减少高 QoS 任务等待低 QoS 队列任务的时间
serialQueue.async {
    // 任务不会阻塞调用线程
}
  • 避免在高 QoS 任务内调用 sync 到低 QoS 队列 → 避免优先级反转

2.3 避免长时间阻塞高 QoS 线程

  • 高 QoS 线程上执行耗时任务 → 阻塞自己或其他高 QoS 任务

  • 优化

    • CPU 密集型 / 耗时操作 → 移到低或中等 QoS 队列
    • 使用 DispatchWorkItem 或后台队列处理

2.4 对共享资源使用 GCD 原语

  • 串行队列Dispatch Barrier管理共享资源
  • 避免高 QoS 任务被低 QoS 任务持有锁阻塞
let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
concurrentQueue.async(flags: .barrier) {
    // 独占访问共享资源
}
  • 可以保证高 QoS 任务访问共享资源时不会被低 QoS 长时间阻塞

2.5 合理拆分任务

  • 避免一个低 QoS 大任务占用串行队列过久
  • 拆成多个小任务,让系统调度机会更多 → 高 QoS 任务不会被长时间阻塞

3️⃣ 总结

方法 GCD/系统机制 开发者措施
缓解优先级反转 QoS 继承:临时提升低 QoS 任务 避免串行队列高低 QoS 混用
异步 vs 同步 async 不阻塞调用线程 避免高 QoS sync 调用低 QoS 队列
阻塞控制 高 QoS 阻塞会影响系统响应 长耗时任务移到低 QoS 队列
共享资源 串行队列 / barrier 保证独占 避免高 QoS 等待低 QoS 持有的锁
任务拆分 系统调度机会更多 将大任务拆成小任务减少阻塞

💡 核心思想:GCD 会临时提升低 QoS 任务优先级,但开发者仍需合理划分任务、队列和资源使用,才能最小化优先级反转对性能的影响。


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

作者 项阿丑
2026年1月28日 11:27

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 时最容易踩的坑有哪些?

作者 项阿丑
2026年1月28日 11:27

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 是生命周期管理三部曲

1-13.【GCD】dispatch_barrier 的作用和使用场景是什么?

作者 项阿丑
2026年1月28日 09:48

1️⃣ 基本作用

dispatch_barrier 用于在并发队列中创建一个“屏障”,保证屏障前的任务全部完成后才执行屏障任务,屏障任务完成后才允许屏障后的任务继续执行。

  • 本质:把并发队列临时变成“串行屏障”
  • 保证 读-写或写-写安全,同时不阻塞其他并行队列的线程资源

2️⃣ 工作机制

假设有一个并发队列 concurrentQueue

let concurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)

concurrentQueue.async { print("A") }
concurrentQueue.async { print("B") }
concurrentQueue.async(flags: .barrier) { print("Barrier") }
concurrentQueue.async { print("C") }
concurrentQueue.async { print("D") }

执行顺序:

  1. A、B 可以同时执行(并发)
  2. BarrierA、B 完全完成 后执行
  3. C、D 等 Barrier 完成后再执行

⚠️ 注意:

  • barrier 任务前后不会并发执行
  • barrier 前的任务可以并发
  • barrier 后的任务也可以并发(但 Barrier 本身是独占的)

3️⃣ 使用场景

3.1 并发队列上的读写安全

典型场景:多读少写的数据结构

var sharedArray = [Int]()
let queue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)

// 多个并发读
queue.async {
    print(sharedArray.count)
}
queue.async {
    print(sharedArray.last ?? 0)
}

// 写操作使用 barrier
queue.async(flags: .barrier) {
    sharedArray.append(42)
}

解释:

  • 多个读可以并发执行 → 高性能
  • 写操作必须等前面读完成,并且执行时阻止其他读写 → 数据安全

Barrier 是 并发队列上的写锁机制


3.2 替代传统锁(NSLock / DispatchSemaphore)

  • 可以用 barrier 替代全局锁对共享资源加锁

  • 优势:

    • 保持 读操作并发性
    • 写操作串行化,不阻塞其他队列的线程池
  • 适合 读多写少场景


3.3 串行队列上的 barrier 无效

let serialQueue = DispatchQueue(label: "serial")
serialQueue.async(flags: .barrier) { ... }
  • barrier 在串行队列上 没有任何特殊作用

  • 原因:

    • 串行队列天然保证同一时间只执行一个任务
    • barrier 只是多余标记

4️⃣ 使用注意事项

  1. 只能在自定义并发队列上使用

    • global queue 不允许 barrier
  2. 不要在主队列上使用 barrier

    • 主队列是串行的,没意义
  3. 同步 barrier(sync + barrier)可能死锁

    • 原因和普通 sync 一样,当前线程可能阻塞自己

5️⃣ 总结

作用:

  • 在并发队列中创建一个排它屏障
  • 屏障前的任务先执行,屏障独占执行,屏障后的任务再继续并发执行

典型场景:

  • 并发读 + 写操作的共享数据
  • 读多写少,避免使用锁仍保证线程安全
  • 替代 NSLock / semaphore 进行高性能读写控制

💡 一句话总结

dispatch_barrier 就是给并发队列加了一道“独占关卡”,保证关键写操作在队列中独占执行,而不影响前后的并发读任务。

1-6.【GCD】自定义队列在底层是如何映射到全局队列的?

作者 项阿丑
2026年1月28日 09:46

自定义队列本身不拥有线程;它们通过 target queue 机制,最终把任务“汇流”到某个全局并发队列(按 QoS)上执行。

下面我们从 结构 → 映射路径 → 调度细节 → 常见误解 一层层拆。


1️⃣ 核心模型:Queue ≠ Thread(再强调一次)

在 GCD 里:

  • 队列(DispatchQueue)
    👉 任务的组织和调度规则
  • 线程(worker thread)
    👉 真正执行任务的资源

👉 自定义队列永远不直接绑定线程


2️⃣ 自定义队列创建时发生了什么?

let queue = DispatchQueue(
    label: "com.example.myqueue",
    qos: .utility,
    attributes: [.concurrent]
)

你得到的是一个:

  • 有自己 FIFO / 并发规则的 逻辑队列
  • 没有线程
  • 没有独立线程池

3️⃣ 真正关键的机制:target queue

每一个 dispatch queue 都有一个 target queue

你可以显式指定:

queue.setTarget(queue: DispatchQueue.global(qos: .utility))

但即使你不写:

系统也会自动帮你设

默认 target 规则是:

队列类型 默认 target
main queue main thread / RunLoop
global queue root queue(系统内部)
自定义 queue 对应 QoS 的 global queue

4️⃣ 调度路径(重点流程)

当你执行:

queue.async {
    work()
}

真实发生的是:

你的自定义 queue
   ↓(保持自己的串行 / 并发语义)
target queue(global queue, by QoS)
   ↓
GCD 调度器
   ↓
共享线程池(worker threads)
   ↓
执行 block

👉 自定义队列只负责:

  • 任务顺序
  • barrier
  • 串行 / 并发约束

👉 全局队列负责:

  • 线程选择
  • QoS 调度
  • CPU 分配

5️⃣ 串行队列是如何“跑在并发队列上”的?

这点很容易让人迷糊。

let serial = DispatchQueue(label: "serial")

虽然:

  • serial queue 自己是 串行的
  • 但它的 target 是 并发的 global queue

为什么还能串行?

原因是:

串行语义是在“队列层”保证的,不是在“线程层”

GCD 保证:

  • 同一时间
  • serial queue 只会向 target queue 提交 一个 block

即使 target 是并发的:

  • 永远只会有一个来自该 serial queue 的任务在执行

6️⃣ 并发自定义队列又是如何工作的?

let concurrent = DispatchQueue(
    label: "concurrent",
    attributes: .concurrent
)

特点:

  • 并发规则由 这个队列自己管理
  • 多个 block 可以同时被提交给 target queue
  • target queue(global)负责并行执行

7️⃣ barrier 为什么只能用于自定义并发队列?

queue.async(flags: .barrier) {
    criticalSection()
}

原因很直白:

  • barrier 是 队列内部的语义

  • global queue 是:

    • 共享的
    • 多来源的
  • GCD 无法也不允许:

    • 为“全局公共队列”插入独占屏障

👉 所以:

barrier 只能作用在你“拥有”的那一层队列


8️⃣ QoS 是如何一路传下去的?

  • 自定义队列有自己的 QoS

  • 提交的 block 继承:

    • queue QoS
    • 或 caller QoS(取更高)
  • 最终:

    • 映射到 target global queue
    • 再映射到线程优先级

👉 QoS 是任务属性,不是队列绑定线程。


9️⃣ 一个容易忽略但很重要的点

setTarget(queue:) 可以改变的不只是 QoS

你可以:

queue.setTarget(queue: someSerialQueue)

结果是:

  • 多个队列“汇流”到一个串行队列

  • 从而实现:

    • 资源限流
    • 顺序控制
    • 自定义调度树

这就是 GCD 的“队列树(queue hierarchy)”设计


🔚 一句话终极总结

自定义队列通过 target queue 机制映射到对应 QoS 的全局并发队列执行;它们只定义任务的组织与同步语义,而线程管理与调度完全由全局队列和系统线程池负责。

1-5.【GCD】全局并发队列(global queue)有多少个?是如何区分的?

作者 项阿丑
2026年1月28日 09:45

✅ 结论版(先背这个)

系统一共有 6 组全局并发队列(global concurrent queues),按 QoS(服务质量)区分;
每一组并不是“一个队列”,而是一类共享同一 QoS 的并发队列入口,最终映射到同一个线程池。


1️⃣ 到底有多少个全局并发队列?

GCD 语义层面看:

🧩 一共 6 种 QoS,对应 6 组全局并发队列

QoS Swift 枚举 用途
User Interactive .userInteractive UI 渲染、动画、立即响应
User Initiated .userInitiated 用户触发、马上要结果
Default .default 默认优先级
Utility .utility I/O、网络、数据处理
Background .background 用户不可见的后台任务
Unspecified .unspecified 系统内部使用

调用方式:

DispatchQueue.global(qos: .userInitiated)

⚠️ 注意:
你拿到的不是“一个独立队列实例”,而是一个“QoS 对应的入口”


2️⃣ 那为什么看起来像“无限多个”?

因为这段代码:

let q1 = DispatchQueue.global(qos: .utility)
let q2 = DispatchQueue.global(qos: .utility)
  • q1 === q2 ❌(对象不一定相等)
  • 但它们调度到的是同一套执行资源

👉 GCD 的设计是:

逻辑上提供多个 queue handle,
物理上共享同一个 QoS 对应的线程池


3️⃣ 全局并发队列是如何“区分”的?

✅ 核心区分维度:QoS(不是线程数)

QoS 决定什么?

  • 线程调度优先级
  • CPU 时间分配
  • 是否更容易抢占别的任务
  • 能否影响 UI 流畅度
  • 能否被系统延迟 / 挂起

例如:

DispatchQueue.global(qos: .background).async {
    // 系统允许你慢慢来
}

DispatchQueue.global(qos: .userInteractive).async {
    // 系统会尽可能立刻执行
}

4️⃣ 那每个 QoS 有多少线程?

👉 没有固定数量

GCD 使用的是:

动态线程池(work-stealing thread pool)

线程数量取决于:

  • CPU 核心数
  • 当前系统负载
  • 任务是否阻塞(I/O)
  • 能耗策略(尤其在 iOS)

例如:

  • 8 核设备
  • .userInitiated 可能同时跑 6–8 个线程
  • .background 可能只给 1–2 个,甚至被暂停

5️⃣ 一个很容易误解的点(重点⚠️)

❌ 错误理解

“每个 QoS 是一个独立的并发队列”

✅ 正确理解

每个 QoS 是一类调度优先级 + 共享线程池,
DispatchQueue.global 只是一个提交入口

你不能:

  • 暂停 global queue
  • barrier global queue
  • 控制它的最大并发数

这些只有 自建并发队列 才能做到。


6️⃣ global queue vs 自建并发队列

特性 global queue 自建 concurrent queue
数量 固定 6 组 任意
QoS 系统定义 可自定义
线程池 系统共享 仍然用系统池
barrier ❌ 不支持 ✅ 支持
suspend
控制并发 部分可控

7️⃣ 一句话总结(面试杀器)

GCD 中并不存在“很多个全局并发队列实例”,而是按 QoS 划分的 6 组全局并发执行环境;DispatchQueue.global(qos:) 只是获取对应 QoS 的调度入口,底层由系统统一的线程池动态执行。

👉 追问:不同 QoS 的 global queue 是否共享线程池?

是的,不同 QoS 的 global queue 在实现层面共享同一个底层线程池,但在调度层面被 QoS 强烈隔离和加权;它们共享“人手”,但不共享“优先权”。

下面把这个听起来有点绕的结论拆开。


1️⃣ 先把“共享”说清楚:共享的是什么?

✅ 共享的是:线程池(worker threads)

  • GCD 并不是:

    • 每个 QoS 一组固定线程
  • 而是:

    • 一个(或少量)全局 worker thread pool
    • 线程是系统资源,可复用

当某个线程空闲时:

  • 可以去执行:

    • .background 的任务
    • 也可以去执行 .userInitiated 的任务
  • 线程本身不“属于”某个 QoS

👉 所以从物理层面讲:是共享的


2️⃣ 那 QoS 的作用在哪?(关键)

QoS 决定的是:调度优先级,不是线程归属

调度时,GCD 会考虑:

  • 任务的 QoS
  • 线程当前的优先级
  • 系统负载
  • 能耗 / 前后台状态

然后做两件事:

🔹 1. 决定“先跑谁”

  • 高 QoS 任务:

    • 更容易被调度
    • 更少被延迟
  • 低 QoS 任务:

    • 可能被推迟
    • 甚至暂时不调度

🔹 2. 提升线程优先级(QoS override)

当一个 worker thread 去执行高 QoS 任务时:

  • 线程的调度优先级会被临时提升
  • 执行完再降回来

👉 线程是“变色龙”,任务决定颜色。


3️⃣ 一个非常重要的机制:QoS 继承 / 提升

来看一个经典场景:

DispatchQueue.global(qos: .background).async {
    DispatchQueue.global(qos: .userInteractive).sync {
        // 高优先级任务
    }
}

如果不做特殊处理:

  • background 线程
  • 执行 userInteractive
  • UI 会卡 💀

所以 GCD 有:

QoS override(优先级反转保护)

  • 当低 QoS 线程执行高 QoS block 时
  • 线程会被临时提升到高 QoS
  • 防止高优先级任务被“拖慢”

这再次说明:
👉 线程池是共享的,但优先级是动态的


4️⃣ 那是不是“完全共享、毫无隔离”?不是。

❌ 不是一个“任务大杂烩池”

系统会做软隔离

  • 高 QoS:

    • 有最低执行保障
    • 更积极唤醒线程
  • 低 QoS:

    • 在资源紧张时被限流
    • 可能暂停

例如:

  • 屏幕锁屏
  • 低电量模式
  • App 进入后台

.background 任务可能直接不跑。


5️⃣ 和 Swift Concurrency 的关系(顺一下)

Swift Concurrency 里的:

Task {
    // 默认继承当前 QoS
}

Task.detached(priority: .background) {
    ...
}

最终:

  • 仍然落到 GCD 的全局线程池
  • TaskPriority → 映射到 GCD QoS

所以:

Swift Concurrency 并没有另起炉灶,它复用的就是这套 QoS + 共享线程池模型


6️⃣ 常见误解对照表

误解 真相
每个 QoS 有自己的一组线程 ❌ 共享线程
background 任务永远慢 ❌ 只是优先级低
高 QoS 一定独占 CPU ❌ 只是更容易被调度
QoS = 线程优先级 ❌ QoS 是任务属性

7️⃣ 一句话终极总结

不同 QoS 的 global queue 在物理层面共享同一个 GCD 线程池,但在调度层面通过 QoS 权重、优先级继承和系统策略实现强隔离,从而既节省线程资源,又保证高优先级任务的响应性。

1-3.【GCD】DispatchQueue 的底层数据结构是什么?

作者 项阿丑
2026年1月27日 17:36

它是一个复杂的 C 结构体,集成了任务队列、线程管理、性能优化等多个子系统

核心数据结构:dispatch_queue_s

// libdispatch 源码中的核心结构(简化)
struct dispatch_queue_s {
    // 1. 基础头部信息(所有 dispatch 对象共享)
    struct dispatch_object_s _as_do[0];      // 转换为 dispatch_object
    struct dispatch_queue_s *_as_dq[0];      // 转换为 dispatch_queue
    
    // 2. 队列标识信息
    const struct dispatch_queue_vtable_s *vtable;  // 虚函数表(多态)
    const char *dq_label;                    // 队列标签(我们设置的 label)
    uint16_t dq_width;                       // 并发宽度(串行为1)
    uint32_t dq_serialnum;                   // 序列号(唯一标识)
    
    // 3. 目标队列和层次结构
    struct dispatch_queue_s *dq_targetq;     // 目标队列(优先级继承)
    uintptr_t dq_targetq_override;           // 目标队列覆盖(QoS 传播)
    
    // 4. 任务队列管理
    union {
        struct dispatch_queue_specific_head_s *dq_specific_head;
        struct dispatch_source_refs_s *ds_refs;
    } _dq_specific;
    
    // 5. 同步原语
    struct dispatch_object_s *volatile dq_items_tail;  // 任务队列尾部
    struct dispatch_object_s *volatile dq_items_head;  // 任务队列头部
    uint32_t dq_atomic_flags;                // 原子标志位
    
    // 6. 线程池和性能管理
    struct dispatch_continuation_s *volatile dq_last;  // 最后执行的任务
    uint32_t dq_side_specific_ints;           // 性能计数器
    pthread_priority_t dq_priority;           // 优先级缓存
};

详细结构解析

1. 多态设计:dispatch_object

// 所有 GCD 对象的基类
struct dispatch_object_s {
    _DISPATCH_OBJECT_HEADER(object);  // 头部宏,包含引用计数等
};

// DispatchQueue 通过以下宏实现多态
#define _DISPATCH_QUEUE_CLASS_HEADER(queue_label, ...) \
    _DISPATCH_OBJECT_HEADER(queue) \
    const char *dq_label; \
    uint16_t dq_width;

// 这使得:
dispatch_queue_t queue = dispatch_queue_create("com.test", NULL);
dispatch_object_t obj = (dispatch_object_t)queue;  // 可以向上转型

2. 任务队列:双向链表

// 实际存储任务的结构
struct dispatch_continuation_s {
    struct dispatch_object_s dc_do;           // 对象头部
    dispatch_function_t dc_func;              // 执行函数
    void *dc_ctxt;                            // 上下文参数
    void *dc_data;                            // 额外数据
    void *dc_other;                           // 关联数据
    
    // 链表指针
    struct dispatch_continuation_s *volatile dc_next;
    struct dispatch_continuation_s *dc_prev;
    
    // 队列关联
    struct dispatch_queue_s *dc_queue;        // 所属队列
};

// 队列如何管理任务
struct dispatch_queue_s {
    // ...
    struct dispatch_continuation_s *dq_items_head;  // 队头
    struct dispatch_continuation_s *dq_items_tail;  // 队尾
    uint32_t dq_nitems;                           // 任务计数
};

3. 队列层次结构

// 队列间的父子关系(目标队列机制)
struct dispatch_queue_hierarchy_s {
    dispatch_queue_t dqh_queue;              // 当前队列
    dispatch_queue_t dqh_target;             // 目标队列
    uintptr_t dqh_override;                  // QoS 覆盖
    uint16_t dqh_priority;                   // 优先级
};

// 示例:
// 自定义队列 → 全局队列 → 根队列
// com.test.queue → com.apple.root.default-qos → kernel

4. 性能优化结构

// 队列的侧面(side)数据结构
struct dispatch_queue_side_s {
    // 用于性能优化的缓存
    uint64_t dq_side_timer;                 // 定时器相关
    uint64_t dq_side_wlh;                   // 工作循环句柄
    uint32_t dq_side_bits;                  // 状态位
};

// 队列特定数据(dispatch_queue_set_specific/get_specific)
struct dispatch_queue_specific_head_s {
    struct dispatch_specific_queue_s *dsq_next;
    void *dsq_data;                         // 用户设置的数据
    uintptr_t dsq_key;                      // 键值
};

不同队列类型的内部差异

1. 全局队列(Global Queue)

// 全局队列有特殊结构
struct dispatch_queue_global_s {
    struct dispatch_queue_s _as_dq[0];      // 基础队列部分
    
    // 全局队列特有
    int dgq_priority;                       // 优先级索引
    unsigned int dgq_flags;                 // 标志位
    
    // 共享线程池引用
    struct dispatch_pthread_root_queue_s *dgq_thread_pool;
    struct dispatch_workloop_s *dgq_wlh;    // 工作循环
};

2. 主队列(Main Queue)

// 主队列的特殊处理
struct dispatch_queue_main_s {
    struct dispatch_queue_s _as_dq[0];
    
    // 与 RunLoop 集成
    CFRunLoopRef _dq_runloop;               // 关联的 RunLoop
    CFRunLoopSourceRef _dq_runloop_source;  // 事件源
    
    // 串行执行保证
    pthread_t _dq_main_thread;              // 主线程标识
    uint32_t _dq_main_flags;                // 主队列标志
};

3. 并发队列 vs 串行队列

// 区别主要在 dq_width 字段:
struct dispatch_queue_s {
    uint16_t dq_width;  // 并发宽度
    // 值为 1:串行队列(DISPATCH_QUEUE_SERIAL)
    // 值 > 1:并发队列(DISPATCH_QUEUE_CONCURRENT)
    // 特殊值:DISPATCH_QUEUE_WIDTH_MAX(并发无限)
};

// 并发队列还有额外的管理结构
struct dispatch_queue_concurrent_s {
    struct dispatch_queue_s _as_dq[0];
    
    // 用于并发控制
    os_unfair_lock _dq_lock;                // 内部锁
    uint32_t _dq_running;                   // 正在运行的任务数
    uint32_t _dq_max_running;               // 最大并发数
};

内存布局示例

// DispatchQueue 在内存中的大致布局
+-----------------------------------+
| dispatch_object_s header          | ← 引用计数、类型信息等
|-----------------------------------|
| vtable pointer                    | ← 虚函数表(决定队列类型行为)
|-----------------------------------|
| dq_label ("com.example.queue")    | ← 队列标签指针
|-----------------------------------|
| dq_width (1 或 n)                 | ← 并发宽度
|-----------------------------------|
| dq_serialnum (0x1234)             | ← 序列号
|-----------------------------------|
| dq_targetq (指向全局队列)          | ← 目标队列指针
|-----------------------------------|
| dq_items_head → [任务1] → [任务2]  | ← 任务链表头
| dq_items_tail → [任务n]            | ← 任务链表尾
|-----------------------------------|
| dq_last (最后执行的任务指针)        | ← 性能优化
|-----------------------------------|
| dq_priority (QoS 值)              | ← 优先级缓存
+-----------------------------------+

队列创建过程

创建自定义队列时的内部操作:

dispatch_queue_t dispatch_queue_create(const char *label, 
                                       dispatch_queue_attr_t attr) {
    // 1. 分配内存
    struct dispatch_queue_s *dq = calloc(1, sizeof(struct dispatch_queue_s));
    
    // 2. 设置基本字段
    dq->dq_label = label ? strdup(label) : NULL;
    dq->dq_serialnum = dispatch_atomic_inc(&g_serialnum);  // 全局递增
    
    // 3. 根据属性设置并发宽度
    if (attr == DISPATCH_QUEUE_SERIAL || attr == NULL) {
        dq->dq_width = 1;  // 串行
    } else if (attr == DISPATCH_QUEUE_CONCURRENT) {
        dq->dq_width = DISPATCH_QUEUE_WIDTH_MAX;  // 并发
    }
    
    // 4. 设置目标队列(通常是全局队列)
    dq->dq_targetq = _dispatch_get_root_queue(qos, overcommit);
    
    // 5. 设置虚函数表
    if (dq->dq_width == 1) {
        dq->vtable = &_dispatch_queue_serial_vtable;
    } else {
        dq->vtable = &_dispatch_queue_concurrent_vtable;
    }
    
    // 6. 初始化任务链表
    dq->dq_items_head = dq->dq_items_tail = NULL;
    
    return dq;
}

任务执行流程数据结构

// 任务提交和执行涉及的数据结构
struct dispatch_queue_workloop_s {
    // 工作循环(每个线程一个)
    pthread_workqueue_t dqw_workqueue;      // 底层工作队列
    uint32_t dqw_refcnt;                    // 引用计数
    uint16_t dqw_qos;                       // QoS 级别
    
    // 任务调度
    struct dispatch_continuation_s *dqw_head;
    struct dispatch_continuation_s *dqw_tail;
    
    // 线程池管理
    uint32_t dqw_thread_pool_size;          // 线程池大小
    uint32_t dqw_thread_pool_active;        // 活跃线程数
};

调试信息

// 可以通过私有 API 查看内部结构(仅调试)
extension DispatchQueue {
    func debugInfo() {
        let queue = self as AnyObject
        
        // 使用 Objective-C 运行时查看内部结构
        let ivars = class_copyIvarList(object_getClass(queue), nil)
        while let ivar = ivars?.pointee {
            let name = String(cString: ivar_getName(ivar)!)
            print("Ivar: (name)")
        }
    }
}

// 或者使用 LLDB 调试
(lldb) po (id)0x600003d6c040  // 假设这是队列地址
// 可以看到内部字段(如果符号可用)

性能优化特性

1. 内联缓存

struct dispatch_queue_cache_s {
    // 缓存最近使用的队列
    struct dispatch_queue_s *dqc_queues[DISPATCH_QUEUE_CACHE_SIZE];
    uint32_t dqc_hits;                      // 缓存命中次数
    uint32_t dqc_misses;                    // 缓存未命中
};

2. 优先级继承

struct dispatch_queue_priority_s {
    uint16_t dqp_priority;                  // 当前优先级
    uint16_t dqp_inherited;                 // 继承的优先级
    struct dispatch_queue_s *dqp_source;    // 优先级来源队列
};

总结

DispatchQueue 的底层数据结构:

  1. 复杂但高效:多层结构体设计,兼顾灵活性和性能
  2. 基于对象模型:所有 dispatch 对象共享基类,实现多态
  3. 链表管理任务:使用双向链表存储待执行任务
  4. 层次化设计:队列有目标队列,形成优先级继承链
  5. 类型特定扩展:不同队列类型有不同字段和虚函数表
  6. 性能优化密集:包含大量缓存、计数器和优化字段

简单来说DispatchQueue 在底层是一个精心设计的任务调度引擎,它:

  • 结构体表示队列(而非类)
  • 链表管理任务
  • 虚函数表实现不同队列类型的行为
  • 目标队列机制实现优先级继承
  • 高度优化以减少内存占用和提高性能

这种设计使得 GCD 能够在极小的开销下处理数千个队列和数百万个任务调度。

👉 追问:任务是如何入队、出队的?

任务入队出队是 GCD 的核心机制,涉及原子操作、无锁队列、优先级调度等多个复杂系统。

核心数据结构关系

// 简化版数据结构关系
struct dispatch_queue_s {
    // 任务链表(无锁队列)
    struct dispatch_continuation_s *volatile dq_items_head;  // 队头
    struct dispatch_continuation_s *volatile dq_items_tail;  // 队尾
    uint32_t dq_nitems;                           // 任务计数
    
    // 线程池引用
    struct dispatch_queue_workloop_s *dq_wlh;     // 工作循环
};

struct dispatch_continuation_s {
    // 任务数据和函数指针
    dispatch_function_t dc_func;      // 要执行的函数
    void *dc_ctxt;                    // 上下文参数
    
    // 链表指针(双向链表)
    struct dispatch_continuation_s *volatile dc_next;
    struct dispatch_continuation_s *dc_prev;
    
    // 标记信息
    uintptr_t dc_flags;              // 标志位(同步/异步/屏障等)
};

1. 入队过程(Enqueue)

异步任务入队(dispatch_async)

// 用户调用
queue.async {
    print("任务执行")
}

// 内部处理流程
func dispatch_async(queue: dispatch_queue_t, block: @escaping () -> Void) {
    // 1. 封装任务
    let continuation = _dispatch_continuation_alloc()
    continuation.dc_func = _dispatch_call_block_and_release
    continuation.dc_ctxt = Block_copy(block)  // 复制block到堆上
    
    // 2. 获取队列状态
    let old_state = queue.dq_state
    
    // 3. 尝试快速路径(无锁操作)
    if _dispatch_queue_try_acquire_async(queue) {
        // 快速路径:队列空闲,直接调度
        _dispatch_continuation_schedule(queue, continuation)
        return
    }
    
    // 4. 慢速路径:需要加锁或队列繁忙
    _dispatch_queue_push(queue, continuation)
}

详细入队步骤

// 实际入队函数
void _dispatch_queue_push(dispatch_queue_t dq, 
                         dispatch_continuation_t dc) {
    
    // 步骤1:设置任务状态
    dc->dc_queue = dq;           // 关联队列
    dc->dc_flags = ASYNC;        // 标记为异步
    
    // 步骤2:原子操作将任务加入链表尾部
    dispatch_continuation_t prev_tail;
    do {
        prev_tail = dq->dq_items_tail;
        dc->dc_prev = prev_tail;           // 设置前驱
    } while (!os_atomic_cmpxchg(&dq->dq_items_tail, 
                                prev_tail, 
                                dc, 
                                relaxed));
    
    // 步骤3:更新前一个节点的next指针
    if (prev_tail) {
        prev_tail->dc_next = dc;          // 连接链表
    } else {
        // 这是第一个任务,更新头指针
        dq->dq_items_head = dc;
    }
    
    // 步骤4:原子递增任务计数
    os_atomic_inc(&dq->dq_nitems, relaxed);
    
    // 步骤5:唤醒工作线程(如果需要)
    _dispatch_queue_wakeup(dq);
}

屏障任务特殊处理

// 屏障任务的入队
void _dispatch_barrier_async(dispatch_queue_t dq, 
                            dispatch_block_t block) {
    
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    dc->dc_func = _dispatch_call_block_and_release;
    dc->dc_ctxt = Block_copy(block);
    dc->dc_flags = BARRIER;               // 关键:设置屏障标志
    
    // 屏障任务需要特殊处理:
    // 1. 插入到队列末尾
    // 2. 标记队列进入"屏障模式"
    // 3. 等待前面所有任务完成
    
    _dispatch_queue_push_barrier(dq, dc);
    
    // 更新队列状态
    dq->dq_atomic_flags |= DISPATCH_QUEUE_IN_BARRIER;
}

2. 出队过程(Dequeue)

工作线程取任务

// 工作线程的主循环
void *_dispatch_worker_thread(void *context) {
    dispatch_queue_t dq = (dispatch_queue_t)context;
    
    while (1) {
        // 步骤1:获取下一个任务
        dispatch_continuation_t dc = _dispatch_queue_pop(dq);
        
        if (dc) {
            // 步骤2:执行任务
            _dispatch_continuation_invoke(dq, dc);
            
            // 步骤3:任务完成后处理
            _dispatch_continuation_free(dc);
        } else {
            // 步骤4:无任务,可能休眠或处理其他队列
            _dispatch_worker_yield_or_exit(dq);
        }
    }
    return NULL;
}

详细出队实现

// 从队列弹出任务
dispatch_continuation_t _dispatch_queue_pop(dispatch_queue_t dq) {
    
    // 步骤1:检查队列状态
    if (dq->dq_nitems == 0) {
        return NULL;  // 队列为空
    }
    
    // 步骤2:处理串行队列(简单)
    if (dq->dq_width == 1) {  // 串行队列
        return _dispatch_queue_pop_serial(dq);
    }
    
    // 步骤3:处理并发队列(复杂)
    return _dispatch_queue_pop_concurrent(dq);
}

// 串行队列出队(简单FIFO)
dispatch_continuation_t _dispatch_queue_pop_serial(dispatch_queue_t dq) {
    
    // 原子操作获取队头
    dispatch_continuation_t head;
    do {
        head = dq->dq_items_head;
        if (!head) return NULL;  // 队列为空
        
        // 尝试将头指针指向下一个任务
    } while (!os_atomic_cmpxchg(&dq->dq_items_head, 
                                head, 
                                head->dc_next, 
                                acquire));
    
    // 如果队列变空,清空尾指针
    if (head->dc_next == NULL) {
        dq->dq_items_tail = NULL;
    }
    
    // 更新任务计数
    os_atomic_dec(&dq->dq_nitems, relaxed);
    
    // 清理链表指针
    head->dc_next = NULL;
    head->dc_prev = NULL;
    
    return head;
}

// 并发队列出队(多线程安全)
dispatch_continuation_t _dispatch_queue_pop_concurrent(dispatch_queue_t dq) {
    
    // 使用原子操作+重试机制
    dispatch_continuation_t task = NULL;
    bool acquired = false;
    
    while (!acquired) {
        // 原子读取队头
        dispatch_continuation_t old_head = dq->dq_items_head;
        
        if (!old_head) {
            return NULL;  // 队列为空
        }
        
        // 尝试获取任务所有权
        acquired = os_atomic_cmpxchg(&dq->dq_items_head, 
                                     old_head, 
                                     old_head->dc_next, 
                                     acquire);
        
        if (acquired) {
            task = old_head;
            
            // 如果这是最后一个任务
            if (task->dc_next == NULL) {
                // 需要原子更新尾指针
                os_atomic_store(&dq->dq_items_tail, NULL, relaxed);
            }
        }
        // 如果失败,说明其他线程抢先获取了,重试
    }
    
    os_atomic_dec(&dq->dq_nitems, relaxed);
    task->dc_next = NULL;
    task->dc_prev = NULL;
    
    return task;
}

3. 任务执行流程

任务执行函数

// 执行任务的函数
void _dispatch_continuation_invoke(dispatch_queue_t dq,
                                   dispatch_continuation_t dc) {
    
    // 步骤1:保存当前队列上下文
    dispatch_queue_t old_dq = _dispatch_thread_getspecific(dispatch_queue_key);
    _dispatch_thread_setspecific(dispatch_queue_key, dq);
    
    // 步骤2:设置线程名字(便于调试)
    if (dq->dq_label) {
        pthread_setname_np(dq->dq_label);
    }
    
    // 步骤3:执行任务函数
    dc->dc_func(dc->dc_ctxt);
    
    // 步骤4:恢复之前的队列上下文
    _dispatch_thread_setspecific(dispatch_queue_key, old_dq);
    
    // 步骤5:如果是同步任务,发送信号
    if (dc->dc_flags & SYNC) {
        _dispatch_semaphore_signal(dc->dc_semaphore);
    }
}

屏障任务的特殊执行

// 屏障任务的执行
void _dispatch_barrier_execute(dispatch_queue_t dq,
                               dispatch_continuation_t dc) {
    
    // 步骤1:等待队列中所有前置任务完成
    while (dq->dq_running > 0) {
        // 忙等待或让出CPU
        _dispatch_hardware_pause();
    }
    
    // 步骤2:执行屏障任务(独占执行)
    _dispatch_continuation_invoke(dq, dc);
    
    // 步骤3:清除屏障标志
    dq->dq_atomic_flags &= ~DISPATCH_QUEUE_IN_BARRIER;
    
    // 步骤4:唤醒等待的后续任务
    _dispatch_queue_wakeup_next(dq);
}

4. 性能优化机制

任务批处理

// 批量处理任务(减少锁开销)
void _dispatch_queue_drain(dispatch_queue_t dq) {
    
    // 尝试一次性取出多个任务
    dispatch_continuation_t batch[16];
    int count = 0;
    
    // 批量出队
    for (int i = 0; i < 16; i++) {
        dispatch_continuation_t dc = _dispatch_queue_pop_fast(dq);
        if (!dc) break;
        
        batch[count++] = dc;
    }
    
    if (count == 0) return;
    
    // 批量执行
    for (int i = 0; i < count; i++) {
        _dispatch_continuation_invoke(dq, batch[i]);
        _dispatch_continuation_free(batch[i]);
    }
}

工作窃取(Work Stealing)

// 当线程空闲时,尝试从其他队列窃取任务
dispatch_continuation_t _dispatch_worksteal(void) {
    
    // 步骤1:获取当前线程的工作队列
    dispatch_queue_t current_queue = _dispatch_thread_get_queue();
    
    // 步骤2:遍历全局队列列表
    for (int i = 0; i < global_queue_count; i++) {
        dispatch_queue_t target = global_queues[i];
        
        // 跳过自己的队列和空队列
        if (target == current_queue) continue;
        if (target->dq_nitems == 0) continue;
        
        // 步骤3:尝试窃取任务
        dispatch_continuation_t stolen = _dispatch_queue_try_steal(target);
        if (stolen) {
            return stolen;  // 窃取成功
        }
    }
    
    return NULL;  // 没有可窃取的任务
}

5. 同步任务特殊处理

dispatch_sync 的实现

void dispatch_sync(dispatch_queue_t dq, dispatch_block_t block) {
    
    // 优化:如果当前已经在目标队列上,直接执行
    if (_dispatch_queue_is_current(dq)) {
        block();
        return;
    }
    
    // 创建同步任务结构
    struct dispatch_sync_context_s {
        dispatch_semaphore_t sema;
        dispatch_block_t block;
        bool done;
    } context;
    
    context.sema = dispatch_semaphore_create(0);
    context.block = block;
    context.done = false;
    
    // 创建任务
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    dc->dc_func = _dispatch_sync_invoke;
    dc->dc_ctxt = &context;
    dc->dc_flags = SYNC;
    dc->dc_semaphore = context.sema;
    
    // 入队
    _dispatch_queue_push(dq, dc);
    
    // 等待任务完成
    dispatch_semaphore_wait(context.sema, DISPATCH_TIME_FOREVER);
    
    // 检查是否成功执行
    if (!context.done) {
        // 发生错误
        dispatch_panic("dispatch_sync failed");
    }
}

// 同步任务执行函数
static void _dispatch_sync_invoke(void *ctxt) {
    struct dispatch_sync_context_s *ctx = ctxt;
    ctx->block();
    ctx->done = true;
}

6. 优先级处理

QoS 传播和提升

// 处理任务的优先级
void _dispatch_queue_adjust_priority(dispatch_queue_t dq,
                                     dispatch_continuation_t dc) {
    
    // 获取任务的 QoS
    qos_class_t task_qos = _dispatch_continuation_get_qos(dc);
    
    // 如果任务优先级高于队列当前优先级
    if (task_qos > dq->dq_priority) {
        // 提升队列优先级
        qos_class_t old_qos = dq->dq_priority;
        dq->dq_priority = task_qos;
        
        // 重新调度队列中的工作线程
        _dispatch_queue_reschedule(dq, old_qos, task_qos);
    }
    
    // 设置执行线程的 QoS
    pthread_set_qos_class_self_np(task_qos, 0);
}

完整流程示例

// 模拟一个任务从提交到完成的完整流程
func exampleTaskLifecycle() {
    let queue = DispatchQueue(label: "com.example", attributes: .concurrent)
    
    // 用户提交任务
    queue.async {
        print("任务执行开始")
        sleep(1)
        print("任务执行结束")
    }
    
    // 内部流程:
    // 1. async() 创建 dispatch_continuation_t
    // 2. 原子操作将任务添加到 queue.dq_items_tail
    // 3. queue.dq_nitems 原子递增
    // 4. _dispatch_queue_wakeup() 唤醒工作线程
    // 5. 工作线程从 queue.dq_items_head 取出任务
    // 6. 调用 dc_func(dc_ctxt) 执行任务
    // 7. 任务完成,dc 被释放
    // 8. 工作线程继续取下一个任务或休眠
}

7. 内存管理优化

任务缓存池

// 避免频繁分配释放 dispatch_continuation_t
struct dispatch_continuation_cache_s {
    dispatch_continuation_t free_list;      // 空闲列表
    uint32_t count;                         // 缓存数量
    os_unfair_lock lock;                    // 保护锁
};

// 获取一个任务结构(优先从缓存取)
dispatch_continuation_t _dispatch_continuation_alloc(void) {
    dispatch_continuation_cache_t cache = &g_continuation_cache;
    
    // 尝试从缓存获取
    os_unfair_lock_lock(&cache->lock);
    if (cache->free_list) {
        dispatch_continuation_t dc = cache->free_list;
        cache->free_list = dc->dc_next;
        cache->count--;
        os_unfair_lock_unlock(&cache->lock);
        
        // 清零复用
        memset(dc, 0, sizeof(struct dispatch_continuation_s));
        return dc;
    }
    os_unfair_lock_unlock(&cache->lock);
    
    // 缓存为空,分配新的
    return calloc(1, sizeof(struct dispatch_continuation_s));
}

// 释放任务结构(放入缓存)
void _dispatch_continuation_free(dispatch_continuation_t dc) {
    dispatch_continuation_cache_t cache = &g_continuation_cache;
    
    os_unfair_lock_lock(&cache->lock);
    if (cache->count < DISPATCH_CONTINUATION_CACHE_MAX) {
        // 放入缓存
        dc->dc_next = cache->free_list;
        cache->free_list = dc;
        cache->count++;
        os_unfair_lock_unlock(&cache->lock);
    } else {
        os_unfair_lock_unlock(&cache->lock);
        free(dc);  // 缓存已满,直接释放
    }
}

总结

任务入队出队的核心机制:

入队过程

  1. 封装任务:将block封装成 dispatch_continuation_t
  2. 原子操作:使用CAS(Compare-And-Swap)将任务添加到链表尾部
  3. 唤醒机制:如果需要,唤醒工作线程处理任务
  4. 优先级处理:根据任务QoS调整队列优先级

出队过程

  1. 原子竞争:多个工作线程竞争获取任务(无锁队列)
  2. 批量处理:可能一次性取出多个任务减少开销
  3. 工作窃取:空闲线程可以从其他队列窃取任务
  4. 屏障处理:屏障任务确保前后任务不交错执行

关键优化

  1. 无锁设计:使用原子操作避免锁竞争
  2. 缓存复用:重用任务结构体减少内存分配
  3. 批量处理:减少上下文切换开销
  4. 优先级继承:高优先级任务能更快执行
  5. 工作窃取:提高CPU利用率

简单比喻

  • 入队 像"快递站收件":你提交包裹(任务),快递站记录到清单(队列),通知快递员(线程)来取
  • 出队 像"快递员派件":快递员从清单取包裹,派送(执行),完成后标记完成
  • GCD 像"智能调度系统":动态分配快递员,优化路线,处理急件优先

这种设计使得 GCD 能够以极低的延迟处理数百万级的任务调度,同时保持高吞吐量和低CPU占用。

1-2.【GCD】GCD 是如何管理线程池的?线程是谁创建的?

作者 项阿丑
2026年1月27日 17:36

线程创建者:GCD 自己

GCD 是线程的实际创建者,但不是随意创建,而是通过高度优化的机制:

// 开发者层面:不直接创建线程
DispatchQueue.global().async {
    // GCD 内部决定:
    // 1. 从池中取出空闲线程
    // 2. 或创建新线程(如果必要)
    // 3. 或将任务排队等待
}

GCD 线程池的智能管理

1. 按需创建,延迟销毁

// GCD 线程生命周期:
// 初始状态:线程池为空
// 第一次提交任务 → 创建1个线程
// 持续提交任务 → 逐渐增加线程数
// 空闲一段时间 → 自动销毁多余线程(节省资源)

2. 线程复用策略

// 类似数据库连接池的模式
class GCDThreadPool {
    private var activeThreads: Set<Thread> = []
    private var idleThreads: Set<Thread> = []
    private var maxThreads: Int
    
    func getThread() -> Thread {
        if let thread = idleThreads.popFirst() {
            // 复用空闲线程
            activeThreads.insert(thread)
            return thread
        } else if activeThreads.count < maxThreads {
            // 创建新线程
            let thread = createThread()
            activeThreads.insert(thread)
            return thread
        } else {
            // 等待线程可用
            return waitForAvailableThread()
        }
    }
}

线程池的关键参数和策略

1. 线程数量限制

// 基于系统资源动态调整
class GCDThreadManager {
    // 主要考虑因素:
    // 1. CPU 核心数(决定最大并发度)
    let maxConcurrentThreads = ProcessInfo.processInfo.processorCount * 2
    
    // 2. 队列类型
    // 串行队列:通常1个线程
    // 并发队列:多个线程,但有限制
    
    // 3. 系统负载
    // 高负载时:减少线程数
    // 低负载时:增加线程数(更快响应)
}

2. QoS(服务质量)影响

// 不同优先级的任务使用不同线程池
DispatchQueue.global(qos: .userInteractive) // 最高优先级,更快获取线程
DispatchQueue.global(qos: .background)      // 最低优先级,可能等待更久

// 内部实现简化:
class QoSThreadPool {
    var highPriorityPool: ThreadPool  // .userInteractive, .userInitiated
    var defaultPool: ThreadPool        // .default
    var lowPriorityPool: ThreadPool    // .utility, .background
    
    func getThread(for qos: QoSClass) -> Thread {
        // 优先从对应优先级的池中获取
        // 高优先级可"借用"低优先级的线程(反之不行)
    }
}

具体工作机制示例

场景:处理大量任务

let queue = DispatchQueue.global()

// 模拟100个任务
for i in 1...100 {
    queue.async {
        sleep(1)  // 模拟1秒工作
        print("任务 (i) 完成,线程: (Thread.current)")
    }
}

// GCD 内部行为:
// 1. 前几个任务:创建新线程(比如4个,基于CPU核心数)
// 2. 继续提交:复用现有线程
// 3. 如果所有线程都忙:排队等待
// 4. 如果长时间排队:可能创建更多线程(但不超过上限)
// 5. 任务完成后:线程空闲,可用于新任务
// 6. 长时间空闲:销毁多余线程

避免的问题和优化

1. 防止线程爆炸

// 传统方式的问题
for _ in 0..<1000 {
    Thread { /* 工作 */ }.start()  // 可能创建1000个线程!
}

// GCD 的优化
let queue = DispatchQueue.global()
for _ in 0..<1000 {
    queue.async { /* 工作 */ }  // 可能只用8-16个线程处理
}

2. 负载均衡

// GCD 内部的工作窃取(Work Stealing)机制
class WorkStealingScheduler {
    var threadPools: [ThreadPool]
    
    func scheduleTask(_ task: Task) {
        // 1. 先尝试分配到最空闲的线程
        // 2. 如果所有线程都忙,但其他队列有闲置线程
        // 3. "窃取"闲置线程来执行任务
    }
}

3. 能耗优化(特别在移动设备)

// iOS/macOS 的能耗管理
class EnergyAwareThreadManager {
    func manageThreads() {
        // 考虑因素:
        // - CPU 频率调整(降频时减少线程)
        // - 电池状态(低电量时减少并发)
        // - 温度控制(过热时限制线程)
        // - 后台状态(后台模式用更少线程)
    }
}

线程生命周期管理

// 简化版 GCD 线程管理逻辑
class GCDThread {
    var state: ThreadState = .idle
    var lastUsed: TimeInterval = 0
    
    enum ThreadState {
        case idle      // 空闲,等待任务
        case active    // 正在执行任务
        case sleeping  // 休眠(可能被销毁)
    }
    
    func run() {
        while true {
            if let task = getTaskFromQueue() {
                state = .active
                task.execute()
                state = .idle
                lastUsed = currentTime()
            } else {
                // 空闲处理
                if shouldDestroyThread() {
                    cleanupAndExit()
                } else {
                    sleepForInterval()
                }
            }
        }
    }
}

特殊情况处理

1. 主线程的特殊性

// 主队列绑定到主线程
DispatchQueue.main.async {
    // 总是运行在主线程
    // GCD 不会为 main queue 创建新线程
    // 而是将任务提交到主线程的 RunLoop
}

// 主线程不是由 GCD 创建的
// 它是应用启动时由系统创建的

2. 同步执行的优化

// sync 调用时的线程优化
queue.sync {
    // 可能的优化:
    // 1. 如果当前线程就是目标队列的执行线程
    // 2. 直接在当前线程执行(避免线程切换)
    // 3. 这称为"队列归属检测"
}

实际调试观察

// 查看线程创建情况
let concurrentQueue = DispatchQueue(label: "test", attributes: .concurrent)

print("初始线程数: (Thread.getAll().count)")

for i in 0..<20 {
    concurrentQueue.async {
        print("任务 (i): (Thread.current)")
        usleep(100_000)  // 100ms
    }
}

// 观察结果:
// 1. 线程数不会无限增长
// 2. 线程会被复用(看到相同的 thread number)
// 3. 最终稳定在一定数量(如 CPU 核心数*2)

总结

GCD 线程管理的关键特点:

  1. 线程创建者:GCD 自己(通过系统调用),开发者不直接创建
  2. 按需创建:首次需要时创建,不是预先创建大量线程
  3. 智能复用:线程执行完任务后回到池中,等待下次使用
  4. 动态调整:根据系统负载、CPU核心数、任务类型动态调整线程数
  5. 上限控制:有最大线程数限制,防止线程爆炸
  6. 自动清理:空闲线程会被销毁,节省资源
  7. 优先级感知:不同 QoS 的任务可能使用不同的线程子池

这种设计使得 GCD 能够:

  • 最大化 CPU 利用率
  • 最小化线程创建/销毁开销
  • 自动适应不同硬件和负载
  • 为开发者隐藏复杂的线程管理细节

简单比喻:GCD 就像一个"智能线程管家",你只需要告诉它"要做什么工作"和"工作的重要程度",它会自动安排最合适的"工人"(线程)来完成,同时保证不雇佣过多工人浪费资源。

👉 追问:GCD 线程池和 pthread / NSThread 的关系?

GCD 线程池、pthread 和 NSThread 之间有清晰的层次关系,pthread 是底层基础,GCD 和 NSThread 都是基于它的高层抽象

层级关系

// 从高到低的抽象层次:
┌─────────────────────────┐
│   GCD (DispatchQueue)   │ ← 最高抽象:任务队列,自动线程管理
├─────────────────────────┤
│        NSThread         │ ← 中级抽象:面向对象的线程封装
├─────────────────────────┤
│       pthread_t         │ ← 底层抽象:POSIX线程,C语言API
└─────────────────────────┘

详细关系解析

1. pthread_t:最底层的基础

// 这是所有线程的根基(包括GCD创建的线程)
#include <pthread.h>

pthread_t thread;
pthread_create(&thread, NULL, worker_func, NULL);  // 创建线程

// GCD 内部最终会调用这个函数来创建线程
// 实际上,macOS/iOS 中的所有线程都是 pthread

2. NSThread:Objective-C 的封装

// NSThread 是对 pthread 的面向对象包装
class NSThread {
    // 内部持有 pthread_t
    private var _pthread: pthread_t
    
    // 创建线程时,内部调用 pthread_create()
    init(block: @escaping () -> Void) {
        _pthread = pthread_create(...)
    }
}

// 验证关系:
Thread.current // 返回当前线程的 NSThread 对象
pthread_self() // 返回当前线程的 pthread_t

// 实际上,Thread.current.pthread 可以获取底层 pthread_t
// (虽然这个属性不公开)

3. GCD 线程池的实现

// GCD 内部结构示意(简化版)
class GCDThreadPool {
    private var threads: [pthread_t] = []
    private var taskQueue: Queue<Task>
    
    func createThreadIfNeeded() {
        // 需要新线程时,创建 pthread
        var thread: pthread_t
        pthread_create(&thread, nil, { context in
            // 线程函数:不断从队列取任务执行
            while let task = taskQueue.dequeue() {
                task.execute()
            }
            return nil
        }, nil)
        
        threads.append(thread)
    }
    
    func execute(_ task: Task) {
        taskQueue.enqueue(task)
        // 如果没有空闲线程且未达上限,创建新线程
        if idleThreads.isEmpty && threads.count < maxThreads {
            createThreadIfNeeded()
        }
    }
}

实际运行时关系示例

场景:观察三者关系

// 创建一个 GCD 并发队列
let queue = DispatchQueue(label: "test", attributes: .concurrent)

// 提交任务
queue.async {
    // 获取三个层面的线程信息
    let nsThread = Thread.current        // NSThread 对象
    let pthreadId = pthread_self()       // pthread_t 标识
    let threadNumber = nsThread.value(forKeyPath: "private.seqNum")  // 内部编号
    
    print("""
    层级关系:
    1. NSThread: (nsThread)
    2. pthread_t: (pthreadId)
    3. 是否GCD创建: (nsThread.name?.contains("com.apple.root") == true)
    """)
    
    // 实际输出可能类似:
    // 1. NSThread: <NSThread: 0x600003d6c040>{number = 7, name = (null)}
    // 2. pthread_t: 0x70000a1000
    // 3. 是否GCD创建: true
}

核心区别对比

特性 GCD 线程池 NSThread pthread_t
抽象级别 最高(队列) 中(对象) 最低(句柄)
创建方式 自动管理 手动创建 手动创建
线程复用 ✅ 自动复用 ❌ 一对一 ❌ 一对一
内存管理 自动 ARC 管理 手动(pthread_join/exit)
跨平台 Apple 生态 Apple 生态 POSIX标准

具体实现细节

1. GCD 如何创建线程

// GCD 内部源码简化示意(libdispatch)
void _dispatch_worker_thread(void *context) {
    // 1. 注册为GCD工作线程
    _dispatch_thread_setspecific(dispatch_queue_key, context);
    
    // 2. 设置线程名字(便于调试)
    pthread_setname_np("com.apple.root.default-qos");
    
    // 3. 进入工作循环
    while (1) {
        // 从队列获取任务
        task = _dispatch_queue_get_task(queue);
        
        if (task) {
            _dispatch_worker_execute(task);
        } else {
            // 空闲处理
            if (should_terminate()) {
                pthread_exit(NULL);
            }
        }
    }
}

// 创建线程的函数
void _dispatch_thread_create(pthread_t *thread, dispatch_queue_t queue) {
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    
    // 配置线程属性
    pthread_attr_set_qos_class_np(&attr, QOS_CLASS_DEFAULT, 0);
    
    // 最终调用 pthread_create
    pthread_create(thread, &attr, _dispatch_worker_thread, (void *)queue);
}

2. NSThread 的 pthread 包装

// NSThread 内部实现示意
@implementation NSThread {
    pthread_t _pthread;
    NSMutableDictionary *_threadDictionary;
}

- (void)start {
    if (_pthread != NULL) return;
    
    // 创建 pthread
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    
    // 存储 self 以便在 C 函数中访问
    NSThread *threadSelf = self;
    
    int err = pthread_create(&_pthread, &attr,
                           _NSThread__start__, (__bridge void *)threadSelf);
    
    pthread_attr_destroy(&attr);
}

static void *_NSThread__start__(void *arg) {
    // 获取 NSThread 对象
    NSThread *thread = (__bridge NSThread *)arg;
    
    // 设置线程特定数据
    pthread_setspecific(NSThreadKey, (__bridge void *)thread);
    
    // 执行目标方法
    [thread main];
    
    return NULL;
}
@end

实际开发中的交互

1. 从 NSThread 获取 pthread

// 方法1:直接获取当前 pthread
let currentPthread = pthread_self()

// 方法2:从 NSThread(不推荐,使用私有API)
extension Thread {
    var pthread: pthread_t? {
        // 注意:这是私有API,App Store审核可能不通过
        let key = "pthread"  // 实际是私有字段
        return self.value(forKey: key) as? pthread_t
    }
}

2. 在 GCD 任务中操作线程

DispatchQueue.global().async {
    // 当前运行在 GCD 管理的 pthread 上
    
    // 可以设置线程名字(用于调试)
    pthread_setname_np("MyWorkerThread")
    
    // 可以获取并操作当前 pthread
    let pthread = pthread_self()
    
    // 可以设置线程优先级(但通常用 QoS 更好)
    var policy: Int32 = 0
    var param = sched_param()
    pthread_getschedparam(pthread, &policy, &param)
    param.sched_priority = 45  // 设置优先级
    pthread_setschedparam(pthread, policy, &param)
    
    // 注意:直接操作 pthread 可能干扰 GCD 的管理
}

3. 混合使用的陷阱

// ❌ 危险的混合使用
let thread = Thread {
    // 在线程中使用 GCD 同步到主队列
    DispatchQueue.main.sync {
        // 如果这个线程恰好是主线程 → 死锁!
        // 因为 Thread 创建的不是主线程,
        // 但理论上如果是主线程调用就会死锁
    }
}

// ✅ 安全的做法
let queue = DispatchQueue.global()
queue.async {
    // 在 GCD 管理的线程中使用 GCD
    DispatchQueue.main.async {
        // 总是安全的
    }
}

性能与开销对比

// 性能测试示例
func measureOverhead() {
    // 1. 直接创建 pthread(最快,但最底层)
    measure("pthread_create") {
        var thread: pthread_t
        pthread_create(&thread, nil, { _ in return nil }, nil)
        pthread_join(thread, nil)
    }
    
    // 2. 创建 NSThread(中等开销)
    measure("NSThread") {
        let thread = Thread { /* do nothing */ }
        thread.start()
        // 需要同步机制确保线程结束
    }
    
    // 3. GCD 提交任务(最低开销,因为复用线程)
    measure("GCD async") {
        let semaphore = DispatchSemaphore(value: 0)
        DispatchQueue.global().async {
            semaphore.signal()
        }
        semaphore.wait()
    }
}

// 典型结果(相对时间):
// pthread_create: 100ms(基准)
// NSThread: 120-150ms(额外对象开销)
// GCD async: 10-20ms(线程复用,极低开销)

调试技巧

1. 查看线程信息

// 在调试器中查看所有线程
(lldb) thread list
// 可以看到:
// * thread #1: tid = 0x1a2b3, 0x0000000100000000, 
//   queue = 'com.apple.main-thread'
// * thread #4: tid = 0x1a2b4, 0x0000000100001000,
//   queue = 'com.apple.root.default-qos'

// 查看线程堆栈
(lldb) bt all
// 会显示所有线程的调用栈,包括GCD工作线程

2. 识别线程来源

// 在代码中判断
if Thread.isMainThread {
    print("这是主线程")
}

// 判断是否为GCD线程
let threadName = Thread.current.name ?? ""
if threadName.hasPrefix("com.apple.root") {
    print("这是GCD线程池中的线程")
}

// 获取线程的QoS级别
let qos = Thread.current.qualityOfService
print("线程QoS: (qos)")

总结

关系链

应用层:GCD (DispatchQueue) → 使用并管理 → NSThread/pthread
          ↓
框架层:NSThread → 封装 → pthread_t
          ↓
系统层:pthread_t → 内核线程

核心要点

  1. pthread 是基石:所有线程最终都是 pthread,包括 GCD 创建的线程

  2. NSThread 是包装:提供面向对象的线程操作,底层仍是 pthread

  3. GCD 是管理者:基于 pthread 构建线程池,提供队列抽象

  4. 线程复用是关键:GCD 复用线程,而直接使用 pthread/NSThread 通常是一对一

  5. 抽象层级不同

    • pthread:关心"如何创建线程"
    • NSThread:关心"如何表示线程对象"
    • GCD:关心"如何组织任务执行"

简单比喻

  • pthread 就像"砖块"(基本单元)
  • NSThread 就像"有标签的砖块"(易于识别和管理)
  • GCD 就像"智能建筑系统"(自动调度砖块完成任务)

在实践中,优先使用 GCD,它提供最优的线程管理和性能。只有在需要特殊线程控制(如长时间后台计算、实时操作)时才考虑使用 NSThread 或 pthread。

1-1.【GCD】DispatchQueue 本质上是什么?它和线程是什么关系?

作者 项阿丑
2026年1月27日 17:35

DispatchQueue 是 GCD(Grand Central Dispatch)的核心抽象,它本质上是一个 任务调度管理器,而不是线程本身。

本质特性

1. 任务队列

  • 存储要执行的任务(闭包或函数)
  • 遵循 FIFO(先进先出)原则
  • 可以是串行(serial)或并发(concurrent)
// 串行队列 - 一次执行一个任务
let serialQueue = DispatchQueue(label: "com.example.serial")

// 并发队列 - 可同时执行多个任务
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)

2. 线程池管理器

  • 管理底层线程池(thread pool)
  • 自动复用线程,避免频繁创建销毁
  • 根据系统负载动态调整线程数

与线程的关系

关键区别

DispatchQueue 线程 (Thread)
高级抽象,任务调度器 低级执行单元
管理任务执行顺序 CPU调度的基本单位
自动线程管理 需要手动管理生命周期
系统优化负载均衡 固定资源占用

实际关系

// 示例:队列与线程的关系
let queue = DispatchQueue(label: "test", attributes: .concurrent)

for i in 1...5 {
    queue.async {
        print("任务 (i) 在线程: (Thread.current)")
    }
}

// 可能输出(线程会被复用):
// 任务1在线程: <NSThread: 0x...>{number = 4, name = (null)}
// 任务2在线程: <NSThread: 0x...>{number = 5, name = (null)}
// 任务3在线程: <NSThread: 0x...>{number = 4, name = (null)} // 线程复用!

重要特性

1. 线程复用

// GCD 自动复用线程,提高性能
// 不会为每个任务都创建新线程

2. 队列层级

// 主队列 - 主线程,UI更新
DispatchQueue.main.async {
    // 更新UI
}

// 全局队列 - 系统提供的并发队列
let globalQueue = DispatchQueue.global(qos: .background)

// 自定义队列
let customQueue = DispatchQueue(label: "custom")

3. 避免线程爆炸

// ❌ 错误:直接创建大量线程
for _ in 0...1000 {
    Thread {
        // 工作
    }.start()
}

// ✅ 正确:使用队列,系统自动管理
let queue = DispatchQueue.global()
for _ in 0...1000 {
    queue.async {
        // 工作 - 系统会合理分配线程
    }
}

最佳实践

1. 选择合适队列

// UI更新用主队列
DispatchQueue.main.async {
    // 更新UI
}

// 后台任务用全局队列
DispatchQueue.global(qos: .userInitiated).async {
    // 耗时任务
    DispatchQueue.main.async {
        // 完成后更新UI
    }
}

2. 避免阻塞主队列

// ❌ 错误:在主队列执行耗时操作
DispatchQueue.main.async {
    let data = processLargeData() // 阻塞UI
    updateUI(data)
}

// ✅ 正确:在后台处理
DispatchQueue.global(qos: .userInitiated).async {
    let data = processLargeData()
    DispatchQueue.main.async {
        updateUI(data)
    }
}

总结

DispatchQueue 是线程的智能调度器

  • 它 管理 任务,让开发者专注于"做什么"
  • 它 隐藏 线程细节,自动优化资源使用
  • 它 保证 任务按照指定顺序和方式执行

GCD 通过 DispatchQueue 将开发者从繁琐的线程管理中解放出来,让并发编程变得更简单、更高效、更安全。

👉 追问:为什么说 GCD 是“基于队列而不是线程”的并发模型?

说 GCD 是"基于队列而不是线程"的并发模型,核心在于它将并发抽象的重心从"谁执行"(线程)转移到了"什么被执行"(任务)以及"如何组织执行"(队列)。

核心思想对比

传统线程模型

// 关注的是"执行者"
let thread1 = Thread { /* 任务A */ }
let thread2 = Thread { /* 任务B */ }
thread1.start()
thread2.start()
// 你需要管理:创建线程、分配任务、协调线程、销毁线程

GCD 队列模型

// 关注的是"任务组织"
let queue = DispatchQueue(label: "work", attributes: .concurrent)
queue.async { /* 任务A */ }  // 我只管提交任务
queue.async { /* 任务B */ }  // 系统决定如何执行
// 你只需要:定义任务、选择队列、提交任务

为什么这个区别很重要?

1. 解耦任务与执行资源

// 传统线程:任务和线程强绑定
Thread {
    downloadImage()  // 任务绑定到这个特定线程
}

// GCD:任务和线程解耦
queue.async {
    downloadImage()  // 任务提交到队列,系统分配线程
}
// 同一个任务在不同时间可能由不同线程执行

2. 从"微观管理"到"宏观调度"

传统线程编程 GCD 队列编程
思考:需要多少线程? 思考:任务如何组织?
担心:线程创建/销毁开销 专注:任务依赖和顺序
操心:线程同步和通信 利用:队列的同步特性

3. 编程模型更直观

// 用队列表达执行顺序非常自然

// 串行执行:天然保证顺序
serialQueue.async { task1() }
serialQueue.async { task2() }  // 一定在 task1 之后

// 并发执行:简单明了
concurrentQueue.async { task1() }
concurrentQueue.async { task2() }  // 可能并行执行

// 依赖关系:清晰表达
queue.async {
    let data = fetchData()
    DispatchQueue.main.async {
        updateUI(with: data)
    }
}

实际体现

示例:对比两种模型的复杂性

// 传统线程方式实现三个任务的串行执行
class ThreadManager {
    var currentThread: Thread?
    
    func executeSequentially() {
        let thread1 = Thread {
            task1()
            // 需要手动协调下一个任务
            let thread2 = Thread {
                task2()
                let thread3 = Thread {
                    task3()
                }
                thread3.start()
            }
            thread2.start()
        }
        thread1.start()
    }
}

// GCD 方式实现三个任务的串行执行
let serialQueue = DispatchQueue(label: "serial")
serialQueue.async { task1() }
serialQueue.async { task2() }
serialQueue.async { task3() }
// 简洁明了,自动保证顺序

示例:避免"线程爆炸"

// 传统方式:容易创建过多线程
for i in 0..<1000 {
    Thread {
        processItem(i)
    }.start()  // 可能创建1000个线程!
}

// GCD 方式:系统智能管理
let queue = DispatchQueue.global()
for i in 0..<1000 {
    queue.async {
        processItem(i)  // 系统复用线程池中的线程
    }
}
// 可能只用 8-64 个线程(根据 CPU 核心数优化)

GCD 的哲学转变

1. 关注点分离

// 你关心的:
- 任务是什么?
- 任务间的依赖关系?
- 任务的优先级?
- 执行顺序要求?

// 系统关心的:
- 用多少线程?
- 哪个线程执行哪个任务?
- 何时创建/销毁线程?
- 如何负载均衡?

2. 更高级的抽象

// GCD 提供了高级原语
let group = DispatchGroup()  // 任务组
let semaphore = DispatchSemaphore(value: 1)  // 信号量
let barrier = DispatchWorkItem(flags: .barrier)  // 屏障

// 这些都在队列层面操作,不直接涉及线程

3. 性能优化透明化

// 系统可以根据情况优化:
// - 线程复用
// - 负载均衡
// - CPU 亲和性
// - 能耗优化(iOS 上特别重要)

// 你只需:queue.async { work }
// 系统决定:用哪个核、哪个线程、何时执行

现实类比

传统线程模型:自己建工厂

  • 你需要:招聘工人(创建线程)
  • 分配任务:A 你去拧螺丝,B 你去焊接
  • 协调管理:A 做完告诉 B
  • 成本高:工人闲置时也要付工资

GCD 队列模型:使用云服务平台

  • 你只需:提交任务需求单(任务到队列)
  • 平台负责:分配工人、调度任务、优化资源
  • 你只关心:任务描述和依赖关系
  • 成本优:按需使用,资源复用

总结

GCD 的"基于队列"模型之所以强大,是因为:

  1. 抽象层次更高:从"执行单元"抽象到"任务组织"
  2. 资源管理更优:系统全局优化,避免局部最优
  3. 编程模型更安全:减少了数据竞争和死锁的机会
  4. 性能更可预测:避免了线程爆炸和过度订阅
  5. 代码更简洁:关注业务逻辑,而不是线程管理

这种设计使得并发编程从一门"黑魔法"变成了更可控、更安全的工程实践。你不再直接指挥"士兵"(线程),而是制定"作战计划"(队列和任务),让"指挥部"(GCD)去最优执行。

❌
❌