我测试的iOS同步锁包括@synchronsized、NSLock、NSCondition、NSConditionLock、NSRecursiveLock、pthread_mutex_t、OSSpinLock。
先贴出测试用的代码:
在模拟器下运行的性能对比(Xcode7.3,iOS 9.3):
在iPhone 6s上运行的性能对比(iOS 9.3):
从以上的输出可以看出:
首先,模拟器和真机测试的结果是有差异的,而且差异还比较大。因而,还是从真机的测试结果来分析同步锁。
从输出结果可以看出,@synchronize的开销是其它同步锁开销的三倍左右,而NSConditionLock与NSRecursiveLock的开销差不多(后者更优一些),剩下三者的性能处于同一级别:OSSpinLock > pthread_mutex_t > NSLock。
@synchronize 的开销很大是因为它必须要设置一个异常处理的块(Exception Handler),这确实是要耗费内部锁的性能。
OSSpinLock则根本不需要进入到内核中,它只是一直不停地去重载(试图访问)这个锁,直到这个锁处于unlock状态。如果锁所保持的时间很长,那情况会变得很糟糕,甚至会出现死锁,但是,它节省了昂贵的系统开销和一对上下文开关。在最新的iOS版本中已经不推荐使用OSSpinLock,因为它已经不是线程安全的了。具体可以参见http://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/?utm_source=tuicool&utm_medium=referral
pthread_mutex_t事实上是当没有竞争的时候,它会使用OSSpinLock来使程序运行流畅,当竞争出现时,它会转而采用更繁重的内核级的同步锁/任务方式。
因此,倘若你的锁是容易出现竞争的锁,那么OSSpinLock就不适合,除非你加锁的区域运行的速度非常非常快。pthread_mutex_t的开销比OSSpinLock稍多一些,但是它避免了OSSpinLock所带来的性能上的浪费。
NSLock是对pthread_mutex_t的很好的封装,除了封装之外,也并不提供别的东西,因此,它的开销不会比pthread_mutex_t多很多。
NSLock与@synchronized都是用的互斥锁,它们就像在共同的实体上实现的两个不同的接口。NSLock创建的是显式锁,而@synchronized创建的是隐式锁。显式锁的好处就是编译器能够很好的理解它,并且处理边界问题,但是它们的行为是相同的。
NSRecursiveLock实际上是一个递归锁,它可以允许统一鲜橙多次加锁,而不会造成死锁。它会跟踪它被lock的次数,每次成功的lock必须平衡调用unlock操作,只有这样锁最后才能被释放。
@synchronized是如何运行的?
在写有@synchronized的代码块中我发现了一些东西:
这是对一个在共享的资源中的简单锁的分解。在这种情况下,我们不需要考虑异常安全的问题,因为它会设置一个exception handler。通过翻看苹果的文档objc_sync.m中关于objc_sync_enter和objc_sync_exit的实现,我们可以知道每一个@synchronized(id obj)锁都包含三组lock/unlock序列。objc_sync_enter调用id2data(对取得与obj相关联的锁负责)来上锁。objc_sync_exit也调用id2data来取得与obj相关联的锁并解锁。同时,id2data必须对它自己内部的数据结构执行加锁/解锁操作来使得它能够安全的获得与obj相关联的锁,因此,我们要对每一对锁付出开销。