iOS 多线程方案技术报告

在 iOS 系统中,多线程方案按照封装抽象程度从低到高排列。开发者通常应优先选择高阶方案,因为它们提供了更好的内存管理和性能优化。

1. 方案综述与对比表

方案名称 技术基础 语言接口 抽象层级 线程生命周期管理 推荐程度
Pthread POSIX 标准 C 底层 程序员手动管理 极低(仅跨平台需求时使用)
NSThread Mach 线程封装 Obj-C / Swift 中低层 程序员手动管理 低(特定监控/长驻线程使用)
GCD 队列 & 线程池 C / Swift 中高层 系统自动管理 高(主流方案)
NSOperation 基于 GCD 封装 Obj-C / Swift 高层(面向对象) 系统自动管理 高(复杂依赖逻辑)
Swift Concurrency 协同线程池 Swift 语言级 编译器/运行时管理 极高(现代开发首选)

2. 核心方案深度解析

A. Pthread (POSIX Threads)

这是跨平台的 C 语言标准接口。

  • 优点:跨平台移植性强。
  • 缺点:API 原始且复杂,需要手动处理线程的创建、销毁、同步等,极易引发内存泄漏或死锁。
  • 建议:在纯 iOS 开发中基本不再使用。

B. NSThread

对底层 Mach 线程的轻量级面向对象封装。

  • 特点:每个 NSThread 对象对应一个实际的内核线程。
  • 优点:可以直接控制线程属性(如优先级、堆栈大小、线程名称)。
  • 缺点:仍需手动管理生命周期;在高并发下频繁创建/销毁线程会产生巨大开销。
  • 使用场景:需要维护一个“长驻线程”(如网络监听轮询)或需要获取当前执行线程对象时。

C. GCD (Grand Central Dispatch)

Apple 推荐的基于“任务”和“队列”的多线程技术。

  • 核心逻辑:开发者只需将任务(Block/Closure)提交到特定的队列(串行或并行),无需关心线程的创建和分配,系统会根据 CPU 负载自动调度。
  • 优点
    • 性能极致:底层使用 C 语言编写,效率极高。
    • 自动管理:利用线程池技术,避免频繁创建线程的开销。
    • 功能丰富:支持延时执行 (asyncAfter)、信号量 (Semaphore)、任务组 (Group)、屏障任务 (Barrier) 等。
  • 缺点:任务一旦提交到队列,难以取消或设置复杂的依赖关系。

D. NSOperation & NSOperationQueue

基于 GCD 的更高层封装,将任务抽象为“操作对象”。

  • 核心逻辑:将任务封装成 NSOperation 对象,放入 NSOperationQueue 中执行。
  • 优点
    • 依赖管理:可以轻松设置操作 A 必须在操作 B 完成后执行(addDependency)。
    • 可取消性:支持对尚未执行的任务进行取消操作。
    • 最大并发数:可以限制同时执行的线程数量。
    • 状态监听:通过 KVO 监听 isExecuting、isFinished 等状态。
  • 使用场景:复杂的后台下载任务管理、具有先后逻辑关系的业务流程。

E. Swift Concurrency (Async/Await)

Swift 5.5 引入的革命性方案,从语言语法层面解决并发问题。

  • 特点:引入 async/await、Task、Actor 等概念。
  • 优点
    • 代码可读性:将异步回调“扁平化”,消除“金字塔回声(Callback Hell)”。
    • 编译时安全:通过 Actor 模型在编译阶段防止数据竞态(Data Race)。
    • 性能优化:采用协同调度(Cooperative Threading),减少线程切换(Context Switch)开销。
  • 建议:新项目及现代 Swift 开发的官方首选方案。

3. 选型建议总结

  1. 简单异步任务:首选 GCD。例如:异步下载图片后回到主线程更新 UI。
  2. 复杂业务流/下载管理器:首选 NSOperation。当你需要暂停、取消任务或设置 A->B->C 这种复杂依赖时,它最方便。
  3. 现代 Swift 项目:首选 Swift Concurrency。它不仅写起来爽,还能帮你规避绝大多数多线程安全漏洞。
  4. 性能监控/低层交互:偶尔涉及 NSThread,用于标识线程或特定的 RunLoop 管理。

GCD 基础知识

是什么

Grand Central Dispatch(GCD) 是 Apple 在 macOS 10.6(Snow Leopard)(发布于 2009-08-28)引入的一个 系统级并发编程框架,在后续的 iOS 4(发布于 2010-06-21)中将 GCD 下放到了移动端,统一了 Apple 全平台并发模型。后续的 NSOperation、Swift Concurrency 都是在 GCD 之上逐步演进的抽象。

GCD 是 Apple 为多核并行运算提供的 C 语言底层解决方案。它通过将线程管理的代码下沉到系统级,让开发者能够以“函数式”的思维处理并发。GCD 的核心库叫 libdispatch,最早由 Apple 内部和 FreeBSD 社区合作完成。深度依赖 Darwin 内核调度机制。后期 libdispatch 开源,成为 Swift Concurrency 的底层基础之一,Swift 的 async/await,最终仍然落在 GCD/内核线程池上。

首次面世的时间

Grand Central Dispatch (GCD) 首次面世的时间是 2009年8月28日,它作为 Mac OS X 10.6 Snow Leopard 操作系统的一部分正式推出

诞生的过程

一、时代背景:多核时代来得比软件准备得更快(2004–2008)

  1. CPU 发展路线发生根本性变化,在 2004 年前后,业界已经明确意识到:
    • 单核频率提升遇到功耗与散热墙
    • CPU 厂商转向:
      • 多核(dual-core / quad-core)
      • SMT / 超线程
      • NUMA 架构

问题是:

操作系统和应用层的软件模型,仍然停留在“一个主线程 + 少量后台线程”的时代。

  1. Apple 面临的现实困境(非常关键)。在 2005–2008 年这段时间,Apple 同时面临几件“叠加危机”:
    1. Mac OS X 普及多核硬件
      • Mac Pro、MacBookPro 快速进入双核、四核
    2. iPhone 项目已经启动(2005年)
      • 极度受限的功耗
      • UI 必须保持 100% 流畅
    3. 开发者的并能能力普遍不足
      • pthread 难用,使用门槛较高
      • 锁的滥用导致:死锁、优先级反转、UI 卡顿

Apple 内部非常清楚一件事:

如果并发模型继续停留在 pthread + lock 层面,多核红利根本吃不到,反而会制造灾难。

二、传统线程模型在 Apple 生态中的失败

  1. pthread 模型的问题(不是不好,而是“不适合 UI 平台”)pthread 的核心假设是:

    • 线程是“重量级资源”
    • 程序员负责:
      • 生命周期:创建、销毁、休眠等。。。
      • 同步
      • 调度策略

    在服务器端尚可接受,但在 Apple 的场景中:

    • UI 线程必须始终响应
    • 后台任务数量不可预测
    • 功耗必须可控

    结果是:

    • 线程数 = 程序员的认知极限
    • 多核 ≠ 高并发
    • 多数 App 实际只用到 1–2 个核
  2. Cocoa / Objective-C 动态系统的矛盾

    • Objective-C 运行时是高度动态的
    • Cocoa API 并不是“默认线程安全”
    • 锁一旦加错:性能雪崩、Bug 极其隐蔽

    Apple 很早就意识到:

    “把并发正确性外包给应用开发者”是行不通的。

三、GCD 的思想来源:不是凭空发明的

  1. “任务(Task)而非线程(Thread)”思想早已存在。GCD 并不是第一个提出这些概念的系统。Apple 的创新点不在“是否首次提出”,而在于:

    把这些思想整合进一个“系统级 API”,并强制推广到整个生态。

  2. Apple 的关键判断(非常重要)
    Apple 在内部做了一个非常激进的判断:

    并发不应该由“线程”作为抽象边界,而应该由“工作单元”作为抽象边界。

    于是有了:

    • block(闭包)作为任务载体
    • queue(队列)作为调度语义
    • 系统负责线程管理

    这一步,直接否定了 pthread 时代的设计哲学。

四、GCD 在 2006–2008 年间的内部孕育

libdispatch 的内部孵化大致时间线(非官方,但符合 Apple 工程节奏):

  • 2006 年左右
    • Apple 内部开始实验性调度系统
    • 与 Core OS 团队深度绑定
  • 2007 年
    • block 语法开始在 Clang 内部推进
    • dispatch queue 原型出现
  • 2008 年
    • 与 Snow Leopard 项目强绑定
    • GCD 成为系统级“卖点”

注意一点:

如果没有 block,GCD 是无法成立的。

block 是 GCD 的“语法基石”,两者是同时推进的。

五、2009 年发布:不是终点,而是“强制转向点”

2009 年 8 月 28 日的发布,本质上是:

Apple 对整个开发者生态的一次“并发模型强制迁移”。

此后发生的事实证明:

  • pthread 在 App 层逐渐退居幕后
  • NSOperationQueue 基于 GCD 重构
  • Swift Concurrency 最终仍然落在 GCD 之上

六:一句高度概括

GCD 并不是为了“让并发更强”,而是为了“让普通开发者在多核时代不至于把系统写崩”。

它的出现,是:

  • 多核硬件不可逆
  • UI 平台容错率极低
  • Apple 不信任开发者手写并发

这三点共同作用下的必然产物。

GCD 提供了什么能力?

我们已经知道,GCD 是一个系统级别的库、或者说底层的并发编程框架,那么查看它的头文件就能知道它都提供了什么功能。对于 iOS 应用开发者而言,理解 GCD 的底层机制和头文件结构至关重要,但这并不意味着需要掌握其提供的所有 API。由于 GCD 是一个系统级框架,其头文件中包含了大量用于系统内部、驱动程序或高级调试的接口。本指南将基于最新的 libdispatch 公开头文件列表(共 15 个),明确区分应用开发者必须掌握的核心 API 和无需掌握的底层/系统级 API。

可以在 Xcode 里通过 Command + 鼠标左键 点击 dispatch_async 函数或者 GCD 有关的其他函数,就能跳转到它的头文件里去了。。。这个应该都知道吧。。。或者使用 Command + Shift + O 然后输入 GCD 的相关函数 API 如 dispatch_async 同样可以进入。。。既然都看这种文章了,相信这些 Xcode 小技巧应该都知道了吧。。。

可以看到 dispatch 的头文件不多,仅 15 个。其中还有部分几乎没有任何 API 都是宏定义以及 dispatch.h 集合所有的头文件。

GCD 头文件结构与 API 分类(15 个公开头文件)

GCD 的公共头文件位于 <dispatch/ 目录下,它们共同定义了 GCD 的全部功能。通过分析这些头文件,我们可以将 GCD 的 API 划分为三个主要类别:核心应用层 API、进阶应用层 API 和系统级底层 API。

头文件 核心功能描述 开发者重要性 建议掌握程度
dispatch.h 总头文件,包含所有其他公共头文件。 极高 了解(通过它引入所有 API)
queue.h 队列创建、任务派发(同步/异步)、主队列、全局队列。 极高 必须掌握
group.h 任务组管理、等待、完成通知。 极高 必须掌握
semaphore.h 信号量,用于资源访问控制和线程同步。 必须掌握
time.h 时间常量和计算,用于延迟执行和超时设置。 必须掌握(配合 queue.hgroup.h
block.h Block 相关的 API,如任务取消、等待。 推荐掌握
source.h 调度源,用于处理系统事件(定时器、文件、信号等)。 进阶学习
once.h dispatch_once,用于单例模式。 Obj-C 项目中掌握
workloop.h dispatch_workloop_t,优先级排序的特殊队列。 极少使用
data.h dispatch_data_t,用于高效处理内存块。 无需掌握
io.h dispatch_io_t,异步文件 I/O。 无需掌握
introspection.h 运行时内省和调试工具 API。 无需掌握
object.h GCD 对象的引用计数和底层管理。 无需掌握
base.h 基础宏定义、类型定义。 无需掌握
dispatch_swift_shims.h Swift 语言桥接层。 极低 无需掌握

日常的 iOS 应用开发基本只需要了解以下 6 个头文件提供的功能就足够了。

queue.h

这是 GCD 的核心。队列是任务的容器,任务派发决定了任务的执行方式(同步或异步)。

API 描述 关键用途
dispatch_queue_create 创建自定义队列(串行或并发)。 实现线程安全(串行队列)或并发执行(并发队列)。
dispatch_get_global_queue 获取系统提供的全局并发队列。 执行后台任务,如网络请求、数据处理。
dispatch_get_main_queue 获取主队列(串行),绑定到主线程。 更新 UI,确保 UI 操作在主线程执行。
dispatch_async 异步派发任务到指定队列,立即返回。 启动后台任务,保持主线程响应。
dispatch_sync 同步派发任务到指定队列,阻塞当前线程直到任务完成。 线程间数据同步,等待结果。(注意:避免在主队列同步派发,否则会导致死锁)
dispatch_barrier_async 异步派发一个屏障任务到并发队列。 实现高效的“多读单写”模式,保证数据安全。

异步函数只能保证任务的执行和提交不在同一个函数调用栈内,并不能保证一定会在不同的线程,这一点很多人可能不理解或者误解了异步。如:

异步主队列时,任务的提交和执行都是在主线程,但是提交任务时的函数调用栈并不会立即执行任务,这个任务的执行要依赖主线程的运行循环(RunLoop)的下一个循环周期。

同步函数看似没有作用,但其实在多线程环境中,用来实现线程同步是非常合适的解决方案。

最常用的场景

1
2
3
4
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"异步执行任务,当前线程:%@", [NSThread currentThread]);
});

需要注意的问题

底层实现原理

group.h

任务组用于监控一组任务的完成状态,是实现多个异步任务完成后执行统一回调的关键。

API 描述 关键用途
dispatch_group_create 创建一个新的任务组。 组织一组相关的异步任务。
dispatch_group_async 将任务与队列、任务组关联并异步派发。 启动任务并自动将其计入任务组。
dispatch_group_enter / dispatch_group_leave 手动增加/减少任务组中的未完成任务计数。 用于非 dispatch_group_async 方式启动的任务,实现手动计数。
dispatch_group_notify 注册一个回调,在任务组中所有任务完成后执行。 统一处理多个异步任务的结果,常用于回到主线程更新 UI。
dispatch_group_wait 同步等待任务组完成,可设置超时时间。 阻塞当前线程,直到所有任务完成或超时。

semaphore.h

信号量是一种低级同步机制,用于控制对有限资源的并发访问数量。

API 描述 关键用途
dispatch_semaphore_create 创建信号量,并设置初始计数值。 限制并发数(如并发下载数)或实现线程同步。
dispatch_semaphore_wait 信号量减 1,如果结果小于 0 则阻塞当前线程。 尝试获取资源,如果资源不足则等待。
dispatch_semaphore_signal 信号量加 1,如果有等待的线程则唤醒一个。 释放资源。

time.h

时间 API 主要用于计算相对时间或绝对时间,常与 dispatch_afterdispatch_group_wait 配合使用。

API 描述 关键用途
dispatch_time 创建一个相对时间(基于当前时间或参考时间)。 计算延迟时间,如 dispatch_time(DISPATCH_TIME_NOW, delayInNanoseconds)
DISPATCH_TIME_NOW / DISPATCH_TIME_FOREVER 时间常量。 表示当前时间或无限期等待。

source.h

调度源允许开发者监听系统底层事件,如文件描述符、Mach 端口、信号、定时器等。对于应用开发者,最常用的是定时器 (DISPATCH_SOURCE_TYPE_TIMER)。

还有 dispatch_after(),虽然 dispatch_after() 的函数声明是写在 queue.h 文件中的,但是它的实现却是在 source.c 中的,而且使用的正是 dispatch_source。

once.h

dispatch_once 确保一个代码块在应用程序的生命周期内只被执行一次,常用于实现线程安全的单例模式。

注意:Swift 中,由于语言特性(如静态属性的延迟初始化),dispatch_once 已经废弃,不再需要手动调用。但在维护旧的 Objective-C 代码时,它仍然是实现单例的标准方式。

162:
死锁?
什么是死锁?
什么情况下产生死锁?

1
2
3
4
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务");
});

以上代码如果是在主线程执行就会死锁。如果不在主线程执行不会产生死锁。

1
2
3
4
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务");
});

以上代码不会产生死锁,异步主队列是在主线程上执行任务。很经典的场景。

1
2
3
4
5
6
7
8
9
10
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("custom_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"执行任务2");
dispatch_sync(queue, ^{
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");

以上代码会产生死锁,两个block任务在同一个队列,又是同步函数

1
2
3
4
5
6
7
8
9
10
11
NSLog(@"执行任务1");
dispatch_queue_t queue1 = dispatch_queue_create("custom_queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("custom_queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
NSLog(@"执行任务2");
dispatch_sync(queue2, ^{
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");

以上代码不会产生死锁,

1
2
3
4
5
6
7
8
9
10
11
NSLog(@"执行任务1");
dispatch_queue_t queue1 = dispatch_queue_create("custom_queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("custom_queue2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue1, ^{
NSLog(@"执行任务2");
dispatch_sync(queue2, ^{
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");

以上代码也不会产生死锁。

1
2
3
4
5
6
7
8
9
10
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("custom_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"执行任务2");
dispatch_sync(queue, ^{
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");

以上代码也不会产生死锁,并发队列

使用同步函数 sync 往当前的串行队列再次添加任务就会产生死锁
往当前串行队列同步派发任务就导致死锁
如果当前队列是串行队列,同步派发任务到当前这个队列会导致死锁

165:
面试题
以下代码的打印结果是什么:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
}

- (void)test {
NSLog(@"2");
}

打印结果是 1,3。performSelector: withObject: afterDelay: 方法依赖 RunLoop,子线程的 RunLoop 默认不开启,导致 2 的打印并不会执行。

166:
performSelector:withObject:afterDelay: 是在 CoreFoundation 库里面实现的
performSelector:withObject: 是在 objc4 库里面实现的

GNUstep 将 Cocoa 的 Objective-C 库重写了一遍并开源了,所以可以参考学习。

167:
面试题2
以下代码的打印结果是什么:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
}];
[thread start];

[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

- (void)test {
NSLog(@"2");
}

打印结果是 1,然后程序崩溃,打印完 1 之后线程退出了,没办法执行 test 了,要保活子线程就得开启 RunLoop,强引用也解决不了。

168:
调度组的使用
实现功能:发送多个网络请求,所有网络请求都完成的时候处理事情?
正是调度组的典型使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("custom_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1,线程:%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2,线程:%@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3,线程:%@", [NSThread currentThread]);
}
});

线程同步方案

169:
多线程存在的问题?同时做多件事情提高了效率,但是存在线程安全问题
资源共享问题:
多个线程访问和使用同一个资源

案例:
存钱取钱。
卖票案例。

解决办法:
线程同步技术:加锁

170-173:
iOS 中的线程同步方案有:

  • OSSpinLock
  • os_unfair_lock
  • pthread_mutex
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized
  • dispatch_semaphore
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)

然后是介绍 OSSpinLock 的使用了。导入 libkern/OSAtomic.h 头文件,OS_SPINLOCK_INIT 宏创建锁,OSSpinLockLock() 函数加锁 OSSpinLockUnlock() 函数解锁
OSSpinLock 叫自旋锁,等待锁的期间一直占用着 CPU,已经过时了,还有可能产生优先级问题。。。总是就是不要用了,那为什么还讲那么多七七八八的呢

原来 172 里是对代码的结构进行了调整。。。结合了一些封装设计在里面。。。站在讲解多线程锁的角度来讲,完全没有必要讲这块内容!
但是我想想有没有必要学习下怎么写的???

173:答疑

174:os_unfair_lock
这是自旋锁还是互斥锁呢?好像没讲呢?根据汇编得知是互斥锁,调用syscall让CPU休眠了

175:pthread_mutex
互斥锁,等待锁的线程会处于休眠状态。

176:pthread_mutex 递归锁

177:自旋锁,互斥锁的汇编实现
自旋锁底层是一个循环一直在执行。
互斥锁底层调用了syscall让CPU进入休眠

178:pthread_mutex 条件
讲了个什么场景没太理解。。。
感觉讲的真不好,总是磕磕绊绊来来回回重复叙述
pthread_cond_wait()
pthread_cond_signal()
意思好像是始终能保证 A 在 B 之前执行?好像是 OperationQueue 里面的依赖关系的?可能就是依赖的底层实现?
生产者?消费者?模式,意思是生产者必须先生产产品出来才可以给消费者消费。

179:
NSLock 是对 mutex 普通锁的封装
NSRecursiveLock 是对 mutex 递归锁的封装
NSCondition 是 mutex cond 的封装

180:课后答疑

181:
[self.confition signal] 解释
完全听不明白了,不知道在表达啥呢。。。
答疑 RunLoop,这不是前面讲过了,看看什么问题

  1. NSThread 的生命不是一个强引用就能保证的,它的 RunLoop 不执行,线程的入口函数执行完就释放了。
  2. 线程的入口函数执行了一段代码,但是如果不实现循环,它就跟普通的命令行程序执行完入口函数就结束了。
    其实还是对线程的理解不够深入。确实线程对象比普通的对象更抽象。

182:NSConditionLock
NSConditionLock?不是有一个 NSCondition 吗,这个又是什么
原来 NSConditionLock 是对 NSCondition 的封装。提供了更多功能
条件锁,应用场景还是建立依赖。。。

183:串行队列
是的,GCD 的串行队列也可以实现线程同步

1
2
3
4
dispatch_queue_t moneyQueue = dispatch_queue_create("custom_queue1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(moneyQueue, ^{

});

这个好理解

184-185:dispatch_semaphore
信号量,通过信号量控制线程最大并发数量为 1 的话同样可以实现线程同步。

dispatch_semaphore_wait() 函数会让信号量减 1。如果信号量大于 0 会执行后续代码。如果信号量 <= 0 会进入休眠直到大于 0 才可以继续执行后续代码。
dispatch_semaphore_signal() 会让信号量加 1。

186:@synchronized 编译器语法糖
是 pthread_mutex_t 的封装。最简单的同步方案。
源码是在 objc4 库的 objc-sync.h objc-sync.mm 文件中,objc_sync_entry() objc_sync_exit()。发现是对 pthread_mutex_t 递归锁的封装。

187:
总结线程同步方案:
性能从高到底

  1. os_unfair_lock
  2. OSSpinLock
  3. dispatch_semaphore
  4. pthread_mutex
  5. dispatch_queue(DISPATCH_QUEUE_SERIAL)
  6. NSLock
  7. NSCondition
  8. pthread_mutex(recursive)
  9. NSRecursiveLock
  10. NSConditionLock
  11. @synchronized

188:自旋锁,互斥锁对比

  • 什么情况使用自旋锁比较划算?
    • 预计线程等待锁的时间很短
    • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
    • CPU 资源不紧张
    • 多核心处理器
  • 什么情况使用互斥锁比较划算
    • 预计线程等待锁的时间较长
    • 单核处理器
    • 加锁的代码有 IO 操作
    • 加锁的代码复杂,循环量大
    • 加锁的代码竞争频繁

189:atomic
属于属性的修饰符,但在 iOS 中只推荐使用 nonatomic。atomic 更多用于 macOS 中。atomic 的作用是使属性的 getter 和 setter 是原子的,意思是属性的 setter 方法和 getter 方法都进行了加锁。
源码在 objc4 中。objc-accessors.mm 中,可以看到 atomic 的情况下进行了加锁。
但是又说没有办法保证使用属性的线程是安全的?这是什么意思
这个还是需要 AI 再详细解读一下了
问清楚为什么在 iOS 中不推荐使用 atomic。

190-192:读写安全
文件的读写安全的实现。

  1. 信号量最大并发为1,可以实现,但是只能单读单写。如果要实现多读单写就不能使用信号量了。多个线程可以同时读取数据,但只有一个线程写入数据,读和写是不能同时进行(只能有一个线程写,且写的时候无法读,可以有多个线程读但读的时候无法写)
  2. pthread_rwlock 读写锁,等待锁的线程会进入休眠
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #import <pthread.h>

    pthread_rwlock_t lock;
    pthread_rwlock_init(&lock, NULL);
    // 读锁
    pthread_rwlock_rdlock(&lock);
    // ......
    pthread_rwlock_unlock(&lock);

    // 写锁
    pthread_rwlock_wrlock(&lock);
    // ......
    pthread_rwlock_unlock(&lock);

    // 释放锁
    pthread_rwlock_destroy(&lock);
  3. dispatch_barrier_async 异步栅栏函数
    1
    2
    3
    4
    5
    6
    7
    8
    // 必须使用创建的并发队列,全局并发队列或串行队列都无效
    dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
    // 读操作...
    });
    dispatch_barrier_async(queue, ^{
    // 写操作...
    });