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 两次会死锁。需要递归锁用 NSRecursiveLockpthread_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_lockdispatch_semaphore
OC 代码,简单易用 NSLock@synchronized
递归函数中加锁 NSRecursiveLock@synchronized
生产者消费者 NSCondition
多步骤顺序协调 NSConditionLock
控制最大并发数 dispatch_semaphore(初始值 N)
多读单写 dispatch_barrier_asyncpthread_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:什么是死锁?怎么排查?

死锁是两个或多个线程互相等待对方持有的资源,导致都无法继续执行。

常见死锁场景

  1. 主线程 dispatch_sync 主队列:主队列在等 sync 任务执行,sync 任务在等主线程空闲。
  2. 串行队列中 dispatch_sync 同一个串行队列。
  3. 两个线程各持一把锁,又等对方的锁(A 持 lock1 等 lock2,B 持 lock2 等 lock1)。
  4. 主线程 dispatch_semaphore_wait 等待一个在主线程回调的异步任务。

排查方法

  • Xcode 暂停(Pause),看主线程和其他线程的调用栈,找到互相等待的位置。
  • 用 Instruments 的 Thread State 分析。
  • 检查 dispatch_syncwait、嵌套锁的使用。

Q2:NSLock 和 @synchronized 怎么选?

  • @synchronized:写法最简单,自动处理异常解锁,是递归锁,适合低频调用、快速保护的场景。
  • NSLock:性能比 @synchronized 好,有 tryLocklockBeforeDate: 等灵活 API,适合高频调用的场景。
  • 高频、性能敏感:用 os_unfair_lockdispatch_semaphore

Q3:多线程操作 NSMutableArray 会崩溃吗?

会。NSMutableArray 不是线程安全的,多线程同时 addObject/removeObject 会崩溃(EXC_BAD_ACCESS)或数据错乱。

解决方案

  1. 所有操作放到同一个串行队列。
  2. @synchronizedNSLock 保护。
  3. dispatch_barrier_async 实现多读单写。
  4. 读操作少的场景,每次读复制一份(copy),写操作加锁。

Q4:atomic 属性是线程安全的吗?

不是。atomic 只保证 setter/getter 是原子操作(不会读到半写的值),但不保证对属性对象的操作是线程安全的。例如 atomicNSMutableArray 属性,多线程 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 中有哪些线程安全的类?

  • 线程安全NSStringNSArrayNSDictionary(不可变版本)、NSCacheNSURLSessiondispatch_queue
  • 非线程安全NSMutableStringNSMutableArrayNSMutableDictionaryUIKit 类(必须在主线程操作)、NSFileManager(部分方法)。

不可变类因为不能修改,所以天然线程安全。可变类需要额外保护。

Q10:Swift 中怎么实现线程同步?

Swift 中没有 @synchronized,可以用:

  • objc_sync_enter(self) / objc_sync_exit(self)(模拟 @synchronized)。
  • NSLockNSRecursiveLock(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

posted @ 2018-09-04 14:12  Mr.陳  阅读(2650)  评论(1)    收藏  举报