iOS开发基础91-线程同步:从锁到信号量、从读写锁到死锁排查
iOS 线程同步:从锁到信号量、从读写锁到死锁排查
多线程编程中,多个线程同时访问同一块共享资源(对象、变量、文件)会引发数据错乱和安全问题。线程同步的目标就是让多个线程按一定顺序安全地访问共享资源。本文从 10 种常见同步技术讲起,深入每种锁的原理与适用场景,补充 atomic/nonatomic、读写锁、死锁排查等,给出完整 OC 代码、Swift 版本对照和常见坑排查。
一、线程同步概述
一句话原理
线程同步就是让多个线程排队访问共享资源,同一时间只有一个线程能操作临界区代码,避免数据竞争导致的错乱和崩溃。
为什么需要线程同步
多个线程同时读写同一块数据时,会出现数据竞争(Data Race):
// 线程 A 和线程 B 同时执行
self.count = self.count + 1;
// 底层是三步:读取 count → 加 1 → 写回 count
// 两个线程可能都读到旧值,都加 1 后写回,结果只加了 1 而不是 2
典型问题:
- 数据错乱:计数不准、数组越界、对象提前释放。
- 崩溃:多线程读写 Mutable 容器(NSMutableArray/NSMutableDictionary)。
- 死锁:两个线程互相等待对方持有的锁。
临界区(Critical Section)
需要同步保护的那段代码叫临界区。锁的粒度要合适:
- 锁太大:性能差,并发度低。
- 锁太小:可能漏掉需要保护的操作。
二、常见线程同步技术
1. OSSpinLock(自旋锁,已废弃)
一句话原理
自旋锁是等锁的线程不睡觉,一直在循环检查锁是否释放(忙等),适合临界区极短的场景。
特点
- 等待线程处于忙等(busy-wait)状态,占用 CPU。
- 优点:临界区极短时性能极高(没有线程切换开销)。
- 缺点:优先级反转——高优先级线程忙等占 CPU,低优先级线程拿不到 CPU 无法释放锁,导致高优先级一直等。
- iOS 10 起已废弃,用
os_unfair_lock替代。
#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
// 临界区
OSSpinLockUnlock(&lock);
新项目不要用 OSSpinLock,已有代码建议迁移到 os_unfair_lock。
2. os_unfair_lock(iOS 10+,推荐)
一句话原理
os_unfair_lock 是自旋锁的替代品,等锁的线程会休眠而不是忙等,同时解决了优先级反转问题,是 iOS 上性能最高的锁。
特点
- iOS 10+ 支持。
- 等待线程进入休眠,不占用 CPU。
- 内部解决了优先级反转问题。
- 性能最高的互斥锁之一。
#import <os/lock.h>
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&lock);
// 临界区
os_unfair_lock_unlock(&lock);
注意:
os_unfair_lock是非递归锁,同一个线程连续 lock 两次会死锁。需要递归锁用NSRecursiveLock或pthread_mutex递归类型。
3. pthread_mutex(互斥锁,跨平台)
一句话原理
pthread_mutex 是 POSIX 标准的互斥锁,等锁的线程休眠,支持普通锁、递归锁、条件锁,是跨平台的底层方案。
3.1 普通互斥锁
#import <pthread.h>
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread_mutex_lock(&mutex);
// 临界区
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
3.2 递归互斥锁
递归锁允许同一个线程多次加锁而不死锁,加锁几次就要解锁几次:
pthread_mutex_t recursiveMutex;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 递归类型
pthread_mutex_init(&recursiveMutex, &attr);
pthread_mutexattr_destroy(&attr);
pthread_mutex_lock(&recursiveMutex);
pthread_mutex_lock(&recursiveMutex); // 同一线程再次加锁,不会死锁
// 临界区
pthread_mutex_unlock(&recursiveMutex);
pthread_mutex_unlock(&recursiveMutex); // 加锁几次解锁几次
pthread_mutex_destroy(&recursiveMutex);
适用场景
- 需要跨平台(iOS/Android/Linux)的 C/C++ 代码。
- 需要精细控制锁的类型和属性。
- 性能优良,普通锁性能接近 os_unfair_lock。
4. NSLock(Foundation 普通锁)
一句话原理
NSLock 是对 pthread_mutex 普通锁的 Objective-C 封装,API 简单,适合 OC 代码。
NSLock *lock = [[NSLock alloc] init];
[lock lock];
// 临界区
[lock unlock];
其他方法
// 尝试加锁,立刻返回 YES/NO,不阻塞
if ([lock tryLock]) {
// 拿到锁了
[lock unlock];
}
// 限时加锁,beforeDate 之前拿到锁返回 YES
if ([lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]) {
[lock unlock];
}
NSLock 是非递归锁,同一线程连续 lock 两次会死锁。
5. NSRecursiveLock(递归锁)
一句话原理
NSRecursiveLock 是对递归互斥锁的 OC 封装,允许同一个线程多次加锁,适合递归函数或循环调用中需要加锁的场景。
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
- (void)recursiveMethod:(int)n {
[recursiveLock lock];
if (n > 0) {
NSLog(@"%d", n);
[self recursiveMethod:n - 1]; // 递归调用,再次加锁不会死锁
}
[recursiveLock unlock];
}
递归锁性能比普通锁略差,只有确实需要递归加锁时才用。
6. NSCondition(条件锁,生产者消费者)
一句话原理
NSCondition 是互斥锁 + 条件变量的封装,线程可以在某个条件不满足时等待(释放锁并休眠),条件满足时被唤醒,典型用于生产者消费者模式。
核心方法
| 方法 | 说明 |
|---|---|
lock / unlock |
加锁/解锁(和 NSLock 一样) |
wait |
释放锁并休眠,直到被 signal/broadcast 唤醒 |
waitUntilDate: |
限时等待 |
signal |
唤醒一个等待的线程 |
broadcast |
唤醒所有等待的线程 |
生产者消费者示例
NSCondition *condition = [[NSCondition alloc] init];
NSMutableArray *products = [NSMutableArray array];
// 生产者
- (void)produce {
[condition lock];
[products addObject:@"product"];
NSLog(@"生产了一个产品,总数:%lu", (unsigned long)products.count);
[condition signal]; // 通知消费者有产品了
[condition unlock];
}
// 消费者
- (void)consume {
[condition lock];
while (products.count == 0) {
[condition wait]; // 没有产品,等待(释放锁并休眠)
}
[products removeObjectAtIndex:0];
NSLog(@"消费了一个产品,剩余:%lu", (unsigned long)products.count);
[condition unlock];
}
注意:
wait会释放锁并休眠,被唤醒后会重新加锁。判断条件要用while而不是if,因为可能被虚假唤醒(spurious wakeup)。
7. NSConditionLock(条件值锁)
一句话原理
NSConditionLock 是对 NSCondition 的进一步封装,用一个整数条件值来控制加锁时机,只有条件值匹配时才能加锁,适合多步骤任务的顺序协调。
// 初始条件值为 0
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:0];
// 线程 A:条件值为 0 时加锁,完成后把条件值设为 1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lockWhenCondition:0];
NSLog(@"步骤一完成");
[conditionLock unlockWithCondition:1]; // 解锁并设置条件值为 1
});
// 线程 B:等待条件值为 1 时才加锁
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lockWhenCondition:1];
NSLog(@"步骤二完成(依赖步骤一)");
[conditionLock unlockWithCondition:2];
});
适用场景:多个任务有严格的先后依赖关系,用条件值驱动执行顺序。
8. Dispatch Semaphore(信号量,推荐)
一句话原理
信号量是一个计数器,控制同时访问资源的最大线程数:计数 > 0 时可以访问并减 1,计数 = 0 时等待;访问完后加 1。初始值为 1 时就是互斥锁。
核心方法
| 方法 | 说明 |
|---|---|
dispatch_semaphore_create(value) |
创建信号量,初始计数值 |
dispatch_semaphore_wait(sema, timeout) |
计数 -1,计数为 0 时等待 |
dispatch_semaphore_signal(sema) |
计数 +1,唤醒一个等待的线程 |
用法一:互斥锁(初始值为 1)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 临界区
dispatch_semaphore_signal(semaphore);
});
用法二:控制最大并发数(初始值为 N)
// 最多同时 3 个任务并发执行
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
for (int i = 0; i < 10; i++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务 %d", i);
sleep(1);
dispatch_semaphore_signal(semaphore); // 任务完成,释放一个名额
});
}
用法三:等待异步任务完成
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self asyncRequestWithCompletion:^{
NSLog(@"请求完成");
dispatch_semaphore_signal(sema); // 计数 +1
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); // 等待,计数为 0 时阻塞
NSLog(@"继续执行");
⚠️ 常见坑:不要在主线程用
dispatch_semaphore_wait等待一个会在主线程回调的异步任务,会造成死锁(主线程被阻塞,回调无法在主线程执行)。
9. Dispatch Queue(串行队列)
一句话原理
串行队列中的任务按顺序一个一个执行,前一个完成后才开始下一个,天然就是线程同步,不需要额外加锁。
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serial", DISPATCH_QUEUE_SERIAL);
// 所有对共享资源的操作都放到这个串行队列
dispatch_async(serialQueue, ^{
// 读写共享资源
});
同步 vs 异步
| 方式 | 说明 | 适用场景 |
|---|---|---|
dispatch_async |
不阻塞当前线程,任务加入队列后立刻返回 | 大多数场景,不阻塞 UI |
dispatch_sync |
阻塞当前线程,等任务完成才返回 | 需要立刻拿到结果时,但要小心死锁 |
⚠️ 死锁坑:在串行队列中
dispatch_sync同一个串行队列会死锁(队列在等 sync 任务完成,sync 任务在等队列当前任务完成)。
线程安全的容器示例
@interface ThreadSafeArray : NSObject
@property (nonatomic, strong) NSMutableArray *array;
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@implementation ThreadSafeArray
- (instancetype)init {
if (self = [super init]) {
_array = [NSMutableArray array];
_queue = dispatch_queue_create("com.example.array", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)addObject:(id)obj {
dispatch_async(self.queue, ^{
[self.array addObject:obj];
});
}
- (id)objectAtIndex:(NSUInteger)index {
__block id obj = nil;
dispatch_sync(self.queue, ^{ // 读用 sync,立刻拿到结果
obj = self.array[index];
});
return obj;
}
@end
10. @synchronized(最简单的递归锁)
一句话原理
@synchronized 是对递归锁的封装,用一个对象作为锁的钥匙,括号内的代码是临界区,写法最简单,适合快速保护小段代码。
@synchronized(self) {
// 临界区
}
原理
- 内部是递归锁(
NSRecursiveLock),同一线程可多次进入。 - 锁的对象是
self或任何 NSObject,相同对象的@synchronized互斥。 - 自动加解锁,即使 block 内抛出异常也会解锁。
常用写法
// 用 self 做锁
@synchronized(self) {
[self.mutableArray addObject:obj];
}
// 用专门的锁对象(推荐,避免 self 被外部也用作锁导致意外互斥)
@property (nonatomic, strong) NSObject *lockObj;
// ...
@synchronized(self.lockObj) {
// 临界区
}
⚠️ 常见坑:
@synchronized(nil)不会加锁,等于没有保护。- 用
self做锁时,如果外部也对这个对象@synchronized,会意外互斥。- 性能比 os_unfair_lock、pthread_mutex 差,高频调用场景不推荐。
三、atomic 与 nonatomic
一句话原理
atomic 是属性的原子性修饰符,保证属性的 setter/getter 是原子操作(多线程同时读写不会得到垃圾值),但不保证线程安全;nonatomic 不保证原子性,性能更好,iOS 开发默认用 nonatomic。
对比
| 特性 | atomic | nonatomic |
|---|---|---|
| setter/getter 原子性 | ✅ 保证 | ❌ 不保证 |
| 性能 | 较低(内部加锁) | 较高 |
| 线程安全 | ❌ 不保证(只保证存取,不保证操作整体安全) | ❌ 不保证 |
| iOS 默认 | 非默认 | 默认(通常都写 nonatomic) |
为什么 atomic 不保证线程安全
@property (atomic, strong) NSMutableArray *array;
// 线程 A
self.array = [NSMutableArray array]; // setter 是原子的
// 线程 B
[self.array addObject:@"x"]; // 拿到 array 是原子的,但 addObject 不是原子的
atomic 只保证 self.array 的读取和赋值是原子的,但拿到数组后的 addObject: 操作不在保护范围内,多线程同时 addObject 仍然会崩溃。
区别:atomic 只保证属性的 setter/getter 是原子操作,不保证整个对象的操作是线程安全的;因为有加锁开销,性能比 nonatomic 差;iOS 开发中几乎都用 nonatomic,线程安全需要开发者自己用锁或串行队列保证。
四、读写锁(多读单写)
一句话原理
读写锁的规则是:同一时间可以有多个线程同时读,但只能有一个线程写,且写的时候不能读。适合读多写少的场景(如缓存、配置)。
方案一:pthread_rwlock
#import <pthread.h>
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
// 读操作(多个线程可同时进入)
pthread_rwlock_rdlock(&rwlock);
// 读取数据
pthread_rwlock_unlock(&rwlock);
// 写操作(独占)
pthread_rwlock_wrlock(&rwlock);
// 写入数据
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_destroy(&rwlock);
方案二:dispatch_barrier_async(推荐,GCD 风格)
一句话原理
在自定义并发队列中,dispatch_barrier_async 的任务会等待前面所有任务完成后才执行,且执行时独占队列(后面的任务等它完成),完美实现写锁;普通 dispatch_async 是读操作,可以并发。
// 必须是自定义并发队列,不能用全局并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.rw", DISPATCH_QUEUE_CONCURRENT);
// 读操作:普通 async,可并发
- (id)readDataForKey:(NSString *)key {
__block id value = nil;
dispatch_sync(concurrentQueue, ^{ // 读用 sync 立刻拿结果
value = self.cache[key];
});
return value;
}
// 写操作:barrier,独占
- (void)writeData:(id)value forKey:(NSString *)key {
dispatch_barrier_async(concurrentQueue, ^{ // 写用 barrier
self.cache[key] = value;
});
}
⚠️ 重要:
dispatch_barrier_async只对自定义并发队列有效,对全局并发队列(dispatch_get_global_queue)和串行队列无效,行为和普通dispatch_async一样。必须自己dispatch_queue_create一个并发队列。
读写锁适用场景
- 缓存(NSCache 本身线程安全,但自定义缓存需要)。
- 配置数据(读多写少)。
- 多读单写的文件操作。
五、性能对比与选择
性能排序(从高到低,仅供参考)
| 锁 | 性能 | 类型 | 说明 |
|---|---|---|---|
| os_unfair_lock | ⭐⭐⭐⭐⭐ | 互斥 | iOS 10+,性能最高,推荐 |
| dispatch_semaphore | ⭐⭐⭐⭐⭐ | 信号量 | 性能极高,推荐,功能灵活 |
| pthread_mutex(普通) | ⭐⭐⭐⭐ | 互斥 | 跨平台,性能优良 |
| pthread_mutex(递归) | ⭐⭐⭐ | 递归 | 比普通锁略差 |
| NSLock | ⭐⭐⭐ | 互斥 | pthread 封装,略差 |
| NSRecursiveLock | ⭐⭐ | 递归 | 递归锁封装 |
| NSCondition | ⭐⭐ | 条件 | 带条件变量,适合生产者消费者 |
| NSConditionLock | ⭐⭐ | 条件值 | 条件值驱动,功能强但性能一般 |
| @synchronized | ⭐⭐ | 递归 | 最简单,但性能最差 |
| OSSpinLock | ⭐⭐⭐⭐⭐ | 自旋 | 已废弃,有优先级反转 |
选择建议
| 场景 | 推荐方案 |
|---|---|
| 简单互斥,性能优先 | os_unfair_lock 或 dispatch_semaphore |
| OC 代码,简单易用 | NSLock 或 @synchronized |
| 递归函数中加锁 | NSRecursiveLock 或 @synchronized |
| 生产者消费者 | NSCondition |
| 多步骤顺序协调 | NSConditionLock |
| 控制最大并发数 | dispatch_semaphore(初始值 N) |
| 多读单写 | dispatch_barrier_async 或 pthread_rwlock |
| 容器线程安全 | 自定义串行队列 或 dispatch_barrier_async |
| 跨平台 C/C++ | pthread_mutex |
六、自旋锁 vs 互斥锁
一句话原理
自旋锁是等锁时忙等(循环检查,占 CPU),互斥锁是等锁时休眠(不占 CPU,被唤醒)。临界区极短用自旋锁,临界区长或竞争激烈用互斥锁。
对比
| 特性 | 自旋锁 | 互斥锁 |
|---|---|---|
| 等待状态 | 忙等,占 CPU | 休眠,不占 CPU |
| 线程切换 | 无切换开销 | 有休眠/唤醒开销 |
| 适用场景 | 临界区极短,竞争少 | 临界区长,竞争激烈 |
| 优先级反转 | 有风险 | 可避免 |
| 代表 | OSSpinLock(已废弃) | os_unfair_lock、pthread_mutex、NSLock |
自旋锁适用情况
- 预计线程等待锁的时间极短。
- 临界区代码经常被调用,但竞争情况较少。
- CPU 资源不紧张,且是多核处理器。
互斥锁适用情况
- 预计线程等待锁的时间较长。
- 单核处理器(自旋锁在单核上纯浪费 CPU)。
- 临界区中有 IO 操作。
- 临界区代码较复杂或循环调用较多。
- 临界区竞争非常激烈。
iOS 上因为 OSSpinLock 已废弃,日常开发基本都用互斥锁(os_unfair_lock 本质也是互斥锁,只是性能极高)。
七、常见问题与坑
Q1:什么是死锁?怎么排查?
死锁是两个或多个线程互相等待对方持有的资源,导致都无法继续执行。
常见死锁场景:
- 主线程
dispatch_sync主队列:主队列在等 sync 任务执行,sync 任务在等主线程空闲。 - 串行队列中
dispatch_sync同一个串行队列。 - 两个线程各持一把锁,又等对方的锁(A 持 lock1 等 lock2,B 持 lock2 等 lock1)。
- 主线程
dispatch_semaphore_wait等待一个在主线程回调的异步任务。
排查方法:
- Xcode 暂停(Pause),看主线程和其他线程的调用栈,找到互相等待的位置。
- 用 Instruments 的 Thread State 分析。
- 检查
dispatch_sync、wait、嵌套锁的使用。
Q2:NSLock 和 @synchronized 怎么选?
@synchronized:写法最简单,自动处理异常解锁,是递归锁,适合低频调用、快速保护的场景。NSLock:性能比@synchronized好,有tryLock、lockBeforeDate:等灵活 API,适合高频调用的场景。- 高频、性能敏感:用
os_unfair_lock或dispatch_semaphore。
Q3:多线程操作 NSMutableArray 会崩溃吗?
会。NSMutableArray 不是线程安全的,多线程同时 addObject/removeObject 会崩溃(EXC_BAD_ACCESS)或数据错乱。
解决方案:
- 所有操作放到同一个串行队列。
- 用
@synchronized或NSLock保护。 - 用
dispatch_barrier_async实现多读单写。 - 读操作少的场景,每次读复制一份(
copy),写操作加锁。
Q4:atomic 属性是线程安全的吗?
不是。atomic 只保证 setter/getter 是原子操作(不会读到半写的值),但不保证对属性对象的操作是线程安全的。例如 atomic 的 NSMutableArray 属性,多线程 addObject 仍然会崩溃。线程安全需要自己加锁。
Q5:dispatch_semaphore 初始值为 0 和 1 有什么区别?
- 初始值 1:用作互斥锁,
wait减 1 变为 0,signal加 1 变回 1。 - 初始值 0:用作"等待信号",
wait时计数为 0 直接等待,必须等别处signal才能继续。典型用于等待异步任务完成。
Q6:dispatch_barrier_async 为什么要用自定义并发队列?
dispatch_barrier_async 的 barrier 行为只对自定义并发队列有效。对全局并发队列(dispatch_get_global_queue),系统不允许 barrier 阻塞整个全局队列(会影响系统其他任务),所以 barrier 退化成普通 dispatch_async。必须自己 dispatch_queue_create("label", DISPATCH_QUEUE_CONCURRENT)。
Q7:递归锁和普通锁的区别?
- 普通锁:同一线程连续
lock两次会死锁(自己等自己释放锁)。 - 递归锁:同一线程可以连续
lock多次,不会死锁,但加锁几次就要解锁几次。内部维护了一个计数器。 - 适用场景:递归函数中需要加锁、方法 A 加锁后调用方法 B,B 也需要加同一把锁。
Q8:NSCondition 的 wait 为什么要用 while 而不是 if?
wait 可能被虚假唤醒(spurious wakeup)——没有 signal 也可能醒来。用 while 可以在醒来后再次检查条件,如果条件仍不满足就继续 wait。用 if 只检查一次,虚假唤醒后会在条件不满足的情况下继续执行,导致逻辑错误。
Q9:iOS 中有哪些线程安全的类?
- 线程安全:
NSString、NSArray、NSDictionary(不可变版本)、NSCache、NSURLSession、dispatch_queue。 - 非线程安全:
NSMutableString、NSMutableArray、NSMutableDictionary、UIKit类(必须在主线程操作)、NSFileManager(部分方法)。
不可变类因为不能修改,所以天然线程安全。可变类需要额外保护。
Q10:Swift 中怎么实现线程同步?
Swift 中没有 @synchronized,可以用:
objc_sync_enter(self)/objc_sync_exit(self)(模拟 @synchronized)。NSLock、NSRecursiveLock(Foundation 可用)。os_unfair_lock(需要桥接)。DispatchQueue(串行队列/barrier)。DispatchSemaphore。- Swift 5.5+:
async/await+Actor(推荐,编译器保证线程安全)。
八、Swift 版本对照
import Foundation
// MARK: - NSLock
let lock = NSLock()
lock.lock()
// 临界区
lock.unlock()
// tryLock
if lock.try() {
// 拿到锁
lock.unlock()
}
// MARK: - NSRecursiveLock
let recursiveLock = NSRecursiveLock()
recursiveLock.lock()
recursiveLock.lock() // 同一线程再次加锁不会死锁
recursiveLock.unlock()
recursiveLock.unlock()
// MARK: - 模拟 @synchronized(Swift 中没有 @synchronized)
objc_sync_enter(self)
// 临界区
objc_sync_exit(self)
// MARK: - DispatchSemaphore
let semaphore = DispatchSemaphore(value: 1)
DispatchQueue.global().async {
semaphore.wait()
// 临界区
semaphore.signal()
}
// 控制最大并发数
let limitSemaphore = DispatchSemaphore(value: 3)
for i in 0..<10 {
limitSemaphore.wait()
DispatchQueue.global().async {
print("任务 \(i)")
sleep(1)
limitSemaphore.signal()
}
}
// MARK: - 串行队列
let serialQueue = DispatchQueue(label: "com.example.serial")
serialQueue.async {
// 临界区
}
// MARK: - 读写锁(barrier)
let concurrentQueue = DispatchQueue(label: "com.example.rw", attributes: .concurrent)
var cache: [String: Any] = [:]
// 读
func read(forKey key: String) -> Any? {
var result: Any?
concurrentQueue.sync {
result = cache[key]
}
return result
}
// 写
func write(_ value: Any, forKey key: String) {
concurrentQueue.async(flags: .barrier) {
cache[key] = value
}
}
// MARK: - NSCondition(生产者消费者)
let condition = NSCondition()
var products: [Any] = []
func produce() {
condition.lock()
products.append("product")
condition.signal()
condition.unlock()
}
func consume() {
condition.lock()
while products.isEmpty {
condition.wait()
}
products.removeFirst()
condition.unlock()
}
// MARK: - os_unfair_lock(需要 import os)
import os
var unfairLock = os_unfair_lock_s()
os_unfair_lock_lock(&unfairLock)
// 临界区
os_unfair_lock_unlock(&unfairLock)
// MARK: - Swift 5.5+ Actor(推荐,编译器保证线程安全)
actor ThreadSafeArray {
private var array: [Any] = []
func append(_ item: Any) {
array.append(item)
}
func get(at index: Int) -> Any {
return array[index]
}
}
let safeArray = ThreadSafeArray()
Task {
await safeArray.append("item")
let item = await safeArray.get(at: 0)
}
九、总结
- 10 种同步技术:
- os_unfair_lock:iOS 10+,性能最高的互斥锁,推荐。
- dispatch_semaphore:性能极高,功能灵活(互斥锁/控制并发数/等待异步),推荐。
- pthread_mutex:跨平台,普通锁/递归锁,性能优良。
- NSLock / NSRecursiveLock:Foundation 封装,OC 代码常用。
- NSCondition / NSConditionLock:条件锁,生产者消费者/多步骤协调。
- 串行队列:天然同步,适合容器线程安全。
- @synchronized:最简单的递归锁,低频场景可用,性能最差。
- OSSpinLock:已废弃,有优先级反转,不要用。
- atomic/nonatomic:atomic 只保证存取原子性,不保证线程安全;iOS 默认用 nonatomic。
- 读写锁:多读单写,用
dispatch_barrier_async(必须自定义并发队列)或pthread_rwlock。 - 自旋锁 vs 互斥锁:临界区极短用自旋,其他用互斥;iOS 上基本都用互斥锁。
- 常见坑:死锁(sync 同队列/主线程 wait 主线程回调)、barrier 用全局队列无效、NSCondition 用 while 不用 if、可变容器非线程安全、atomic 不保证线程安全。
- 选择原则:简单场景用
@synchronized/NSLock,性能敏感用os_unfair_lock/dispatch_semaphore,多读单写用dispatch_barrier_async,容器安全用串行队列,Swift 新项目优先考虑Actor。

浙公网安备 33010602011771号