当多线程访问共享资源的时候,会出现资源竞争的情况,导致数据错乱的问题,OC中要想实现线程间的同步的话,有以下手段:
一、锁
锁就是实现多线程同步的其中一种方案,查看示例程序,
总数是15程序执行完成应该是0,然而并不是,现在要做的就是让多线程对共享资源这里是
1 | int preCount = self.totalCount; |
能够顺序执行。
1.1、自旋锁
所谓的自旋锁,形如以下形式
1 | while( 抢锁 == 没抢到 ) { |
只要没有抢到锁,程序就会一直重试,所以会一直占用CPU资源。
1.1.1 OSSpinLockLock
跟踪下OSSpinLockLock的汇编代码内部确实是一个while循环,循环部分的代码如下
1 | 0x104ba98b1 <+12>: cmpl $-0x1, %eax |
OSSpinLockLock特点:
- 效率高,因为一直占用着cpu,其实这个是优点也是缺点
- 会出现优先级反转(对于优先级不同的线程,使用同一把锁访问共同资源,优先级低的线程先拿到锁,这时优先级高的线程过来后,由于锁被占用,但是优先级高,一直占有CPU资源,导致持有锁的低优先级的线程无法执行释放锁的操作,从而优先级高的线程一直处于忙等的状态)的问题;
- 无法处理递归。
OSSpinLockLock使用见demo
1.2、互斥锁
由于自旋锁一直占用着cpu资源
1 | while( 抢锁 == 没抢到 ) { |
其实不需要一直循环等待,只要当检测到锁被占用了,那么就去睡觉,当锁的状态改变了,通知它就行了,这就是互斥锁。
1 | while (抢锁 == 没抢到) { |
1.2.1 os_unfair_lock
由于OSSpinLockLock存在优先级反转的问题,os_unfair_lock是苹果用来替代OSSpinLockLock的。跟踪了汇编代码,等待的汇编代码如下
跟踪路径:os_unfair_lock_lock -> _os_unfair_lock_lock_slow -> __ulock_wait
1 |
|
一旦执行到syscall时,该条线程就去睡觉了。
os_unfair_lock特点:
- 没有优先级反转的问题;
- 由于线程睡觉,当然唤醒也是要时间的,效率没有自旋锁高;
iOS10.0以上才可用;- 也不能处理递归的场景。
os_unfair_lock使用见demo
1.2.2 pthread_mutex_t
这个是一个比较底层的api,在C和C++都可以使用,这个对于没有抢到的资源也是睡觉,所以是互斥锁,通过断点可以查看休眠的代码
1 | libsystem_kernel.dylib`__psynch_mutexwait: |
找到该代码的路径为pthread_mutex_lock -> pthread_mutex_firstfit_lock_slow -> _pthread_mutex_firstfit_lock_wait -> __psynch_mutexwait
pthread_mutex_t特点:
- 没有优先级反转的问题;
- 由于线程睡觉,当然唤醒也是要时间的,效率没有自旋锁高;
- 可以处理递归场景;
- 可以增加条件锁。
1.2.3 NS—LOCK
下面介绍Foundation框架下的lock类,其实NSLock就是对pthread_mutex_t的一个上层封装,使其更加面向对象:
NSLock是对上面提到的pthread_mutex_t普通锁用法的封装NSCondition和NSConditionLock是对上面提到的pthread_mutex_t条件锁用法的封装NSRecursiveLock是对上面提到的pthread_mutex_t递归锁用法的封装。
2中方案验证下观点:
- 参考
GNUStep的源码,地址找到Foundation代码; - 通过
hopper工具查看/System/Library/Frameworks/Foundation.framework
gnu不是苹果源码,但是有很大的参考价值,截取部分代码如下
1 | @implementation NSLock |
再通过hopper工具最后确认下
分别看下init和lock和unlock
1.2.3.1 NSLock



使用的demo地址
1.2.3.2 NSCondition



使用的demo地址
1.2.3.3 NSConditionLock
这个是对NSCondition的封装,扩展了功能而已

主要是lockWhenCondition和unlockWithCondition,看下GNU的代码
1 | - (void) lockWhenCondition: (NSInteger)value |
有了源码就很好理解了。
使用的demo地址
1.2.3.4 NSRecursiveLock



使用的demo地址
通过逆向验证了我们的观点,所以不用测试论速度的话肯定不如pthread_mutex_t,毕竟objc_msgSend也是要时间的。
1.2.4 Synchronized
使用方式很简单
1 | - (void)reduceCount { |
通过汇编代码看下synchronized多线程等待的实现,代码修改见提交,断点在此处,拦截第二次断点,注意是第二次。

objc_sync_enter -> os_unfair_recursive_lock_lock_with_options -> _os_unfair_lock_lock_slow -> __ulock_wait
1 | libsystem_kernel.dylib`__ulock_wait: |
发现到最后也是调用了syscall,从_os_unfair_lock_lock_slow开始和上面的os_unfair_lock一样。
Synchronized的源码是开源的在objc的objc-sync.mm文件中
1 | int objc_sync_enter(id obj) |
本质是对os_unfair_lock的一个封装
1 | typedef struct os_unfair_recursive_lock_s { |
特点如下:
- 没有优先级反转的问题;
- 由于线程睡觉,当然唤醒也是要时间的,效率没有自旋锁高;
- 可以处理递归场景;
- synchronized的内部维护了一个map表,性能稍微其他的差些。
二、串行队列
我们要实现线程同步的根本原因是当多线程访问统一共享资源的时候会出现数据错乱的问题,那么只要能够保证多线程执行共享资源任务的时候能够顺序执行就可以了,那么串行队列也是一种方案。使用也很简单
1 |
|
三、信号量
使用如下:
1 | - (instancetype)init { |
- 通过
dispatch_semaphore_create定好信号量的数量; dispatch_semaphore_wait调用则self.semaphore会减1,如果减1以后<=0 ,那么就会等待,知道>0;dispatch_semaphore_signal会将self.semaphore加1。
四、性能对比
关乎各种方案的性能对比yykit的作者做了个代码比较,地址
性能从高到低排序:
- os_unfair_lock
- OSSpinLock
- dispatch_semaphore
- pthread_mutex
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
其实,不通过代码测试也能猜出个大概了:
- 自旋锁高于互斥锁
- 越接近底层的api肯定是更快的,所以c和GCD的会快于
oc(上层封装)的;
五、推荐
开发中如果对性能要求比较高的话比较常见的选择:
- 常规锁的话,推荐使用
dispatch_semaphore和pthread_mutex; - 递归锁的话,推荐使用
pthread_mutex(recursive)和NSRecursiveLock(rac中用的是这个)
说明 本文的完整demo地址。