各种锁的性能分析
double_t beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < 100000; i++) {
//加锁
//解锁
}
double_t endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"时间间隔:%f:ms",(endTime - beginTime)*1000);
复制代码
通过上面的方法对不同的锁进行性能测试得到如下结果:
发现@synchronized相对其他锁的效率比较低,但是相差不大
断点调试
@synchronized
调用的时候断点,然后通过汇编查看@synchronized
底层调用了什么方法
objc_sync_enter
单看这个逻辑很简单,
- 将传入的对象分装得到一个
SyncData
对象,对SyncData对象进行data->mutex.lock()
加锁处理 - 如果对象为nil,抛出提示,调用objc_sync_nil,不做任何处理
objc_sync_exit
这个实际上更简单
- 取出
SyncData
对象,调用data->mutex.tryUnlock()
解锁
SyncData结构分析
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
复制代码
nextData
:又嵌套一个类型,由此可次看出可能是一个链表结构object
:对传入的对象做了一层包装threadCount
:调用锁里面任务的线程数量recursive_mutex_t
:递归锁
id2data,data的存储
TLS:线程局部存储(Thread Local Storage,TLS)用来将数据与一个正在执行的指定线程关联起来
源码分析
- 第一步:StripedMap
SyncData **listp = &LIST_FOR_OBJ(object);
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
}
#endif
复制代码
会有一个全局的hash标StripedMap链表来存储对象。其中他在真机的大小是8
- 第二步:
tls_get_direct
从当前线程的局部存储tls中,能否找到object对应的SyncData, 然后根据:ACQUIRE
/RELEASE
操作,对应的lockcount++/lockCount--; lockcount表示当前线程被锁的次数。 - 第三步:fetch_cache
从缓存中快速获取
object
对应的SyncData
,如果cache中存在就和第二步的操作一样。 - 第四步:从链表里面找看有没有匹配的
这一步实际上从全局的链表里面找,看有没有匹配的,如果找到,goto done,创建缓存。
- 第五步:创建一个全新的SyncData对象
创建一个全新的对象,利用头插法,插入到链表中
- 第六步:创建cache缓存和TLS存储
拉链的产生
入上图所示,我们通过多线程使用@synchronized,为了出现hash冲突,修改StripeCount为1,然后调试源码,就会得到拉链的结果:
总结:@synchronized实际上就是在一般锁的基础上加了一些多线程的处理,这样多线程使用就更加安全。